The goal of this little program I am working on is to draw a red circle while radiobutton1 is checked, and draw a black circle while radiobutton2 is checked.
Below is my code, the problem with this code is when radiobutton1 is checked, it does draw red circles, but then if I click radiobutton2, then all the red circles will turn black. Then if check radiobutton1 again, the all the dots will turn red again.
How do I keep both color circles on the panel?
List<Point> points = new List<Point>();
Graphics g;
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
points.Add(e.Location);
panel1.Invalidate();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
int count = 0;
if (radioButton1.Checked)
{
g = e.Graphics;
while (count < points.Count())
{
g.FillEllipse(Brushes.Red, points[count].X, points[count].Y, 10, 10);
count++;
}
}
else if (radioButton2.Checked)
{
g = e.Graphics;
while (count < points.Count())
{
g.FillEllipse(Brushes.Black, points[count].X, points[count].Y, 10, 10);
count++;
}
}
}
The way graphics works in Windows Forms is, the Paint method redraws the entire panel.
You are already drawing every point every time the paint method executes: this is correct.
But you have no way of remembering which point is supposed to be which color, so all you have to go on when you do the painting is the current values of the radiobuttons. You need some way of recording the current color when you add a circle.
One way to do this would be to define a Circle class which stores the location and color of a circle:
class Circle
{
public Point Location { get; set; }
public Brush Fill { get; set; }
}
Then instead of points being a List<Point>, it can be a List<Circle>, and when you see a mouse click, you can add a new Circle instead of a Point:
var circle = new Circle()
{
Location = e.Location,
Fill = radioButton1.Checked ? Brushes.Red : Brushes.Black
};
points.Add(circle);
And when you do the painting, you can check each circle's color as you draw them - all you have to do is this:
foreach (var circle in points)
{
e.Graphics.FillEllipse(circle.Fill, circle.Location.X, circle.Location.Y, 10, 10);
}
Note that you do not need a member level Graphics g - and it's a bad idea to keep a Graphics object after the Paint method has finished. It will not necessarily still be valid later. Always just use e.Graphics.
I also replaced your while loop with a simpler foreach.
If I understand your intent correctly, you need to keep two lists of points, one for each color. Then, when you click somewhere, put the clicked point in the appropriate list (red or black). Then, in your Paint event handler, replace the conditional code with two loops, one through each list of points (drawing the points from the red list in red and the points from the black list in black).
Code:
List<Point> redPoints = new List<Point>();
List<Point> blackPoints = new List<Point>();
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
if (radioButton1.Checked)
redPoints.Add(e.Location);
else
blackPoints.Add(e.Location);
panel1.Invalidate();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
int count = 0;
Graphics g = e.Graphics;
foreach (Point p in redPoints)
{
g.FillEllipse(Brushes.Red, p.X, p.Y, 10, 10);
}
foreach (Point p in blackPoints)
{
g.FillEllipse(Brushes.Black, p.X, p.Y, 10, 10);
}
}
Note: if your circles overlap one another and you care about maintaining the layering order (first-clicked circles drawing first), then #Blorgbeard's solution is better because it keeps all the circles in the same list, thus maintaining the original layering. Feel free to switch the accepted answer.
Related
I found out how to draw Rectangles and some code to find when two rectangles overlap but I can't connect these procedures.
I have the two rectangles that I wanted but then a cannot determine whether these intersect, to then add this information to a ListBox.
Here is my code:
public partial class Form1 : Form
{
Graphics g;
Pen p;
Point cursor;
int k = 0;
Point[] tocke = new Point[2];
public Form1()
{
InitializeComponent();
g = this.CreateGraphics();
p = new Pen(Color.Black, 3);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
cursor = this.PointToClient(Cursor.Position);
statusMisa.Text = "X: " + cursor.X + " Y: " + cursor.Y;
}
private void Form1_Click(object sender, EventArgs e)
{
bool Preklapanje(int l1x, int l1y, int l2x, int l2y, int r1x, int r1y, int r2x, int r2y)
{
if (l1x >= r2x || l2x >= r1x)
{
return false;
}
if (l1y <= r2y || l2y <= r1y)
{
return false;
}
return true;
}
List<int> pozX = new List<int>();
List<int> pozY = new List<int>();
if (checkCrtanje.Checked == true)
{
Rectangle rect = new Rectangle(cursor.X - 50, cursor.Y - 50, 100, 100);
if (k < 2)
{
g.DrawRectangle(p, rect);
tocke[k++] = new Point(cursor.X, cursor.Y);
listBox1.Items.Add("X: " + cursor.X + " Y: " + cursor.Y);
pozX.Add(cursor.X);
pozY.Add(cursor.Y);
}
else
{
MessageBox.Show("Možeš nacrtati samo dva kvadrata!");
}
}
if (k == 3)
{
if (Preklapanje(pozX[0] - 50, pozY[0] - 50, pozX[0] + 50, pozY[0] + 50, pozX[1] - 50, pozY[1] - 50, pozX[1] + 50, pozY[1] + 50))
{
listBox1.Items.Add("Preklapaju se.");
}
else
{
listBox1.Items.Add("Ne preklapaju se.");
}
}
}
}
► As noted, you shouldn't use CreateGraphics() to draw on a Control's surface: this object becomes invalid (belongs to the past) as soon as the Control where the drawing is performed is invalidated (is repainted).
All Controls that have a drawable surface, raise a Paint event and have an OnPaint method that we can override to perform custom painting (the OnPaint method is responsible to raise the Paint event, so we need to handle just one).
The argument of both the event and the method, represents a PaintEventArgs object, which provides a fresh Graphics object that we can use to paint.
We can call the Invalidate() method to repaint a Control when needed. This method causes the generation of a new PaintEventArgs object (thus, a new Graphics object). After, the OnPaint method is called, which - in turn - raises the Paint event.
► To determine whether a Rectangle intersects another (or more than one), we can use the Rectangle.IntersetWith() method (it returns true or false) and the Rectangle.Interset() method → this is used to generate a Rectangle that represents the intersection of two other rectangles.
See also:
→ Rectangle.Contains([Rectangle])
→ Rectangle.Union([Rectangle a], [Rectangle b]).
Here, I'm using a few collections to store the shapes currently drawn and their intersections (just rectangles, but you can build more complex shapes using GraphicsPath objects):
A List<Rectangle> (rects) which stores the Rectangles already created.
A List<Rectangle> (intersections), to store the intersections which belong to the past (intersections already drawn).
A List<Rectangle> (currentIntersects), used to temporarily store the intersection generated when a new Rectangle shaped is being drawn, so we can use different colors (as soon as we release the Mouse Button, this collection is fixed and added to the intersections collection).
A Rectangle structure (currentRect) which represents the Rectangle that is currently being drawn on the surface (when the Mouse Button is released, this object is added to the rects collection).
A Point structure (startPosition), used to store the initial position of the Rectangle currently drawn. It's reset when the OnMouseDown method is called (when a new Rectangle shape is generated).
► To use this code, create a new Form and paste the code you find here in its Code file. No need to subscribe to any event: since we're drawing on a Form, I'm overriding its methods (OnPaint, OnMouseDown, OnMouseUp, OnMouseMove), no event is used.
You can do the same with a Custom Control or a UserControl.
To add these collection, or just the intersections collection, to e.g., a ListBox, to handle the collections visually, see here (the currentIntersects and intersections collections already contain the information):
How to call a method that uses PaintEventArgs and coordinates variables
NOTE:
► Here, in the OnPaint method override, I'm not calling base.OnPaint(), so the event is not generated. This speeds up the process a bit, but keep in mind that subscribing to the Form's Paint event is useless.
► You need to activate double-buffering: (set DoubleBuffered = true), otherwise you'll notice a lot of flickering (this is quite normal).
This is how it works:
public partial class FormDrawings : Form
{
private List<Rectangle> rects = new List<Rectangle>();
private List<Rectangle> intersections = new List<Rectangle>();
private List<Rectangle> currentIntersects = new List<Rectangle>();
private Rectangle currentRect = Rectangle.Empty;
private Point startPosition = Point.Empty;
private float penSize = 2.0f;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Left) {
startPosition = e.Location;
currentRect = new Rectangle(startPosition, Size.Empty);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left) {
if (e.Y < startPosition.Y) { currentRect.Location = new Point(currentRect.X, e.Y); }
if (e.X < startPosition.X) { currentRect.Location = new Point(e.X, currentRect.Y); }
currentRect.Size = new Size(Math.Abs(startPosition.X - e.X), Math.Abs(startPosition.Y - e.Y));
currentIntersects.Clear();
foreach (var rect in rects) {
if (currentRect.IntersectsWith(rect)) {
currentIntersects.Add(Rectangle.Intersect(currentRect, rect));
}
}
this.Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (currentRect.Size != Size.Empty) rects.Add(currentRect);
if (currentIntersects.Count > 0) {
intersections.AddRange(currentIntersects);
}
}
protected override void OnPaint(PaintEventArgs e)
{
using (var borderPen = new Pen(Color.LightGreen, penSize))
using (var iBrush = new SolidBrush(Color.FromArgb(128, Color.Orange)))
using (var crBrush = new SolidBrush(Color.FromArgb(128, Color.DeepSkyBlue))) {
intersections.ForEach(r => e.Graphics.FillRectangle(iBrush, r));
currentIntersects.ForEach(r => e.Graphics.FillRectangle(crBrush, r));
e.Graphics.DrawRectangle(borderPen, currentRect);
rects.ForEach(r => e.Graphics.DrawRectangle(borderPen, r));
}
}
}
I am trying to draw circles using Bitmap.
Each time I click the mouse, the circle I previously drew is moved to the new position.
What I want to happen is: Each time I click the mouse, a new circle is created/drawn at the position I clicked and all previously drawn circles remain without moving.
I am working with the following code:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace multirectangle
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
}
Bitmap background;
Graphics scG;
Rectangle rectangleObj;
private Point clickCurrent = Point.Empty;
private Point clickPrev = Point.Empty;
private void Form1_Load(object sender, EventArgs e)
{
background = new Bitmap(Width, Height);
rectangleObj = new Rectangle(10, 10, 30, 30);
scG = Graphics.FromImage(background);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
clickCurrent = PointToClient(Cursor.Position);
clickPrev = clickCurrent;
if (clickPrev == Point.Empty) return;
rectangleObj.X = clickPrev.X - rectangleObj.Height / 2;// +radius;
rectangleObj.Y = clickPrev.Y - rectangleObj.Width / 2;
Refresh();
}
protected override void OnPaint(PaintEventArgs pe)
{
pe.Graphics.DrawImage(Draw(), 0, 0);
}
public Bitmap Draw()
{
Graphics scG = Graphics.FromImage(background);
Pen myPen = new Pen(System.Drawing.Color.Red, 3);
scG.Clear(SystemColors.Control);
scG.DrawEllipse(myPen, rectangleObj);
return background;
}
}
}
Your English was a little confusing. If I'm understanding your problem correctly, right now the only thing that's being drawn is the new circle where the click was, and you want all the old ones to persist as well? In which case, there are two options:
Don't clear the bitmap before you draw. scG.Clear(SystemColors.Control); will clear the bitmap you just drew. If you remove that line and don't clear the bitmap, then the next time you click, it will then draw the new ellipse right on top of the last bitmap.
If you want a fresh drawing/bitmap everytime, you would need a list of your rectangleObj . Each time you click, you add that point to your rectangleObj collection. Then in your draw method, you would iterate through the collection and draw all of them.
I notice a few things. First, in Form1_MouseDown(), you have this:
clickCurrent = PointToClient(Cursor.Position);
clickPrev = clickCurrent;
You are overwriting the old position (clickPrev) before you even save it. If you want to keep both positions, you should put them in a simple structure, like a List. When you get a new point, just Add() it to the list. Then, in your Draw() routine, loop over all the elements in the list and draw them all.
If you just want two positions--and only two--just swap your statements like this:
clickPrev = clickCurrent;
clickCurrent = PointToClient(Cursor.Position);
And you'll have to allocate another rectangle object for the drawing, although it would make more sense to take care of this in the Draw() routine.
Swap the position of the following statements
clickCurrent = PointToClient(Cursor.Position);
clickPrev = clickCurrent;
I think you are assigning the clickCurrent to clickPrevious after you initialize clickCurrent. It needs to be the other way.
Please try this
Rectangle rectangleObj;
Bitmap background;
Graphics scG;
Pen myPen;
private void Form1_Load(object sender, EventArgs e)
{
rectangleObj = new Rectangle(10, 10, 30, 30);
background = new Bitmap(Width, Height);
scG = Graphics.FromImage(background);
myPen = new Pen(Color.Red, 3);
BackgroundImage = background;
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
var point = PointToClient(Cursor.Position);
rectangleObj.X = point.X - rectangleObj.Height / 2;
rectangleObj.Y = point.Y - rectangleObj.Width / 2;
scG.DrawEllipse(myPen, rectangleObj);
Refresh();
}
OnPaint and Draw methods removed. As well as clickCurrent and clickPrev fields.
When you change the form size (for example, maximize it), Bitmap and Graphics remain the same, so you get this effect. To avoid this, you need to add the event handler
private void Form1_SizeChanged(object sender, EventArgs e)
{
background = new Bitmap(Width, Height);
scG = Graphics.FromImage(background);
BackgroundImage = background;
}
Note that each time you resize the form, all previously drawn is erased. If this is undesirable, a different approach is needed for drawing. Let me know, I will give another example.
In the designer I have two TextBoxes.
And also a Chart control.
I want that when I type in the first textBox the number 120 and in the second one type the number 1 it will draw a point on the chart in 120,1 but I mean 120 and 1 as axis x and axis y values.
The red filled circle is not at 120 and 1.
I mean that the red circle should be drawn on the left axis on 120.
And if I will put instead 120 116 and instead 1 25 then the circle should be drawn at the left axis 116 and on the bottom axis on 25.
But now the circle is drawn out of the chart.
This 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;
using System.Windows.Forms.DataVisualization.Charting;
using System.Drawing.Drawing2D;
using System.Collections;
namespace Test
{
public partial class Form1 : Form
{
private Point startPoint = new Point();
private Point endPoint = new Point();
private int X = 0;
private int Y = 0;
private List<Point> points = new List<Point>();
private Point lastPoint = Point.Empty;
private ArrayList myPts = new ArrayList();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Random rdn = new Random();
for (int i = 120; i > 0; i--)
{
chart1.Series["Series1"].Points.AddXY
(rdn.Next(0, 10), rdn.Next(0, 10));
}
chart1.Series["Series1"].ChartType = SeriesChartType.FastLine;
chart1.Series["Series1"].Color = Color.Red;
ChartArea area = chart1.ChartAreas[0];
area.AxisX.Minimum = 1;
area.AxisX.Maximum = 30;
area.AxisY.Minimum = 1;
area.AxisY.Maximum = 120;
LineAnnotation line = new LineAnnotation();
Point p1 = new Point(1, 120);
chart1.Annotations.Add(line);
line.AxisX = area.AxisX;
line.AxisY = area.AxisY;
line.IsSizeAlwaysRelative = false;
line.X = 1; line.Y = 120;
line.Right = 30; line.Bottom = 1;
line.LineColor = Color.Blue;
line.LineWidth = 3;
}
SolidBrush myBrush = new SolidBrush(Color.Red);
private void chart1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
foreach (Point p in myPts)
g.FillEllipse(myBrush, p.X, p.Y, 10, 10);
}
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
myPts.Add(new Point(X,Y));
chart1.Invalidate();
}
private void txtT_TextChanged(object sender, EventArgs e)
{
X = int.Parse(txtWeight.Text);
}
private void txtDays_TextChanged(object sender, EventArgs e)
{
Y = int.Parse(txtDays.Text);
}
}
}
What I did is that after I enter both textBoxes values then when I click anywhere on the Chart control area with the mouse it should draw the circule on the coordinates from the TextBoxes.
But the circle is not drawing on the right place.
The textBox name txtT is the left one the axis on the left values.
The textBox txtDays should is the axis on the bottom values.
The task of translating drawing coordinates into DataPoints and back is not exactly intuitive.
It is possible but you need to know the rules and pay a certain price.
I have outlined the way in this post and it is worth looking into..
But as the problem is coming up repeatedly, here is a more general solution.
Here is how to call it:
private void button11_Click(object sender, EventArgs e)
{
valuePoints.Add(new PointF(640, 1));
valuePoints.Add(new PointF(670, 10));
paintToCalaculate = true;
chart1.Invalidate();
}
And here is the result: Two red points drawn at the values 640, 1 and 670, 10:
The points are placed correctly event though I have zoomed and scrolled in the chart and also resized it..
To make it work we need three class level variables: A flag and two point lists. One list is the input with the values in the chart where the points are, the other is the output, receiving the current pixel coordinates.
bool paintToCalaculate = false;
List<Point> drawPoints = new List<Point>();
List<PointF> valuePoints = new List<PointF>();
I use a flag to avoid recalculating the Points when the system causes redraws. And after setting it I trigger the Paint event by Invalidating the Chart. During the Paint event I reset the flag.
Please note that these values are very volatile: They change:
Whenever you zoom or scroll
Whenever you resize the chart
Whenever the layout changes, maybe because new points need room or trigger a change in a label format..
Therefore those drawing coordinates will have to be updated on each such event!
Here is one example, that takes care of zoom and scrolling:
private void chart1_AxisViewChanged(object sender, ViewEventArgs e)
{
paintToCalaculate = true;
chart1.Invalidate();
}
You need to add these two lines, or a function to wrap them, to a few other spots in your program, depending what things you allow to happen in the chart.. Resize is also an obvious candidate..
Now for the actual caculation. It is using the ValueToPixelPosition, which does all the work. Unfortunately is only works inside of any of the three paint events of a chart (PrePaint,Paint and PostPaint) . Here I use the normal Paint event.
private void chart1_Paint(object sender, PaintEventArgs e)
{
if (paintToCalaculate)
{
Series s = chart1.Series.FindByName("dummy");
if (s == null) s = chart1.Series.Add("dummy");
drawPoints.Clear();
s.Points.Clear();
foreach (PointF p in valuePoints)
{
s.Points.AddXY(p.X, p.Y);
DataPoint pt = s.Points[0];
double x = chart1.ChartAreas[0].AxisX.ValueToPixelPosition(pt.XValue);
double y = chart1.ChartAreas[0].AxisY.ValueToPixelPosition(pt.YValues[0]);
drawPoints.Add(new Point((int)x, (int)y));
s.Points.Clear();
}
paintToCalaculate = false;
chart1.Series.Remove(s);
}
//..
// now we can draw our points at the current positions:
foreach (Point p in drawPoints)
e.Graphics.FillEllipse(Brushes.Red, p.X - 2, p.Y - 2, 4, 4);
}
Note that I add and remove a dummy Series and add and clear one Point to it for each data point, just to calculate its pixel coordinates. Yes, a bit involved, but the results are worth it..
I assume you can change the Button_Click code to read in the values from your TextBoxes instead of the using my hard-coded numbers..
I have small Image for a circle and I want to make the following:
Whenever some place on my form is clicked, I want to add a new instance of that circle in that place if there is no other circle there already.
I was thinking about a list of Circles and when that click happens i check the list to see if none of its circles is overlapping before adding the new one but I don't have any experience with forms so i don't know what would be the best approach for that.
You can build up a GraphicsPath and check if the clicked point is inside any of its parts with the IsVisible method.
This code also builds up a list of the points and to draws the image to each of it in the Paint event. If you let the GraphicsPath do the drawing you uncomment the DrawPath line and delete these //** list related lines.
GraphicsPath GP = new GraphicsPath();
List<Point> PL = new List<Point>(); //**
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
int diameter = 22; // put in the size of your circle
Size s = new Size(diameter, diameter);
if (!GP.IsVisible(e.Location))
{
Point middle = new Point(e.X - diameter / 2, e.Y - diameter / 2);
GP.AddEllipse(new Rectangle(middle, s));
PL.Add(middle); //**
}
this.Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
// e.Graphics.DrawPath(Pens.Firebrick, GP);
Image img = new Bitmap("D:\\circle22.png"); //**
foreach(Point pt in PL) e.Graphics.DrawImage(img, pt); //**
img.Dispose(); //**
}
I have a task of drawing a path on a panel using mouse click. The path should be some thing like left
click and release at (x1, y1), move the mouse and then left click and release at (x2, y2). Then a line should be drawn from (x1, y1) to (x2, y2). Now this time when I move my mouse to other location say (x3,y3) and then left click & release, a line should be drawn from (x2, y2) to (x3,y3).
In this manner I want to draw a path with multiple vertices say up to (Xn, Yn).
Currently I could able to draw only from (x1, y1) to (x2, y2).
using (Pen draw_pen = new Pen(Color.Blue, PEN_WIDTH))
{
g.DrawLine(draw_pen, _StartPt.X, _StartPt.Y, _EndPt.X, _EndPt.Y);
}
Does anyone can let me know whether this is achievable ? If yes can you please provide sample code snippet. Thanks in advance.
Use a list or a collectible of your choice, then whenever a click/letgo happens add the current point to your list, after adding draw over your complete list, always from one element to the next until the end.
You can use a GraphicsPath to represent it. Here's a simple example:
public partial class Form1 : Form
{
private int PEN_WIDTH = 5;
private Point lastPoint = new Point(-1, -1);
private System.Drawing.Drawing2D.GraphicsPath GP = new System.Drawing.Drawing2D.GraphicsPath();
public Form1()
{
InitializeComponent();
this.Paint += Form1_Paint;
this.MouseDown += Form1_MouseDown;
}
void Form1_MouseDown(object sender, MouseEventArgs e)
{
Point pt = new Point(e.X, e.Y);
if (lastPoint.X == -1 && lastPoint.Y == -1)
{
lastPoint = pt;
}
else
{
GP.AddLine(lastPoint, pt);
this.Refresh();
}
lastPoint = pt;
}
void Form1_Paint(object sender, PaintEventArgs e)
{
using (Pen draw_pen = new Pen(Color.Blue, PEN_WIDTH))
{
Graphics g = e.Graphics;
g.DrawPath(draw_pen, GP);
}
}
}
Obviously you could track the number of clicks if you wanted to stop after a certain number of vertices. You could use a List<GraphicsPath> to represent more than one set of lines.