I'm creating a path that will be followed by a rectangle. I want to make it with more "tick" points, since on each timer tick I draw it on the next point on the made curve. When it reaches the curve's turn, it goes smoothly, since there are more points at the curve, but at the straight parts of the path, it skips a lot of distance to reach the next points on the list. How can I make the PathPoints with more points? Or is there a better approach to what I'm trying to do? The approach that I'm using, I found it on the internet.
public Form1()
{
InitializeComponent();
//avoid flickering
this.DoubleBuffered = true;
//make a bitmap to display
_bmp = new Bitmap(100, 100);
using (Graphics g = Graphics.FromImage(_bmp))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.FillRectangle(Brushes.Aquamarine, new Rectangle(_bmp.Width / 2 - 2, _bmp.Height / 2 - 2, 10, 10));
}
//cleanup
this.FormClosing += delegate { if (_bmp != null) _bmp.Dispose(); if (_gPath != null) _gPath.Dispose(); };
//setup a path and add some random values
_gPath = new System.Drawing.Drawing2D.GraphicsPath();
List<Point> fList = new List<Point>();
//add points that will let the picturebox be fully visible inside the form
Point middle = new Point(this.ClientSize.Width / 2, this.ClientSize.Height / 2);
fList.Add(new Point(this.ClientSize.Width / 2, this.ClientSize.Height));
fList.Add(new Point(middle.X - 20, middle.Y + 20));
fList.Add(new Point(0, this.ClientSize.Height / 2));
//fList.Add(new Point(this.ClientSize.Width / 2, this.ClientSize.Height / 2));
//add a curve by these values
_gPath.AddCurve(fList.ToArray());
//flatten, to make the path a Path of lines and points
_gPath.Flatten();
//get these points as Array to locate the picturebox
this._points = _gPath.PathPoints;
//add the handler for the paint-event
this.Paint += new PaintEventHandler(Form1_Paint);
//start the timer
t.Tick += new EventHandler(t_Tick);
t.Interval = 100;
t.Start();
Console.WriteLine(_points.Length);
}
void Form1_Paint(object sender, PaintEventArgs e)
{
if (_gPath != null && _bmp != null)
{
//draw the image
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
e.Graphics.DrawPath(Pens.Black, _gPath);
e.Graphics.DrawImage(_bmp, (int)_points[_i].X - (_bmp.Width / 2), (int)_points[_i].Y - (_bmp.Height / 2));
}
}
void t_Tick(object sender, EventArgs e)
{
t.Stop();
//redraw
this.Invalidate();
_i++;
if (_i >= _points.Length)
_i = 0;
t.Start();
}
}
To make more points you should overload for Flatten method:
_gPath.Flatten(null, (float) 0.1);
Also I would recommend to organize Animation loop to have interval of 40ms. So it would be 25 frames per second. (1000s / 25 = 40 ms)
t.Interval = 40;
Related
In fact , I want to draw circle in new position each time double-click and without remove before circle ,It should be noted that, I used PictureBox.
public Point postionCursor { get; set; }
List<Point> points = new List<Point>();
private void pictureBox1_DoubleClick(object sender, EventArgs e)
{
postionCursor = this.PointToClient(new Point(Cursor.Position.X - 25, Cursor.Position.Y - 25));
points.Add(postionCursor);
pictureBox1.Invalidate();
pictureBox1.Paint += new PaintEventHandler(pic_Paint);
}
private void pic_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
foreach (Point pt in points)
{
Pen p = new Pen(Color.Tomato, 2);
SolidBrush myb = new SolidBrush(Color.White);
g.DrawEllipse(p, postionCursor.X, postionCursor.Y, 20, 20);
g.FillEllipse(myb, postionCursor.X, postionCursor.Y, 20, 20);
p.Dispose();
}
}
You're not using the pt variable in the foreach loop.
foreach (Point pt in points)
{
using(Pen p = new Pen(Color.Tomato, 2))
using(SolidBrush myb = new SolidBrush(Color.White))
{
g.FillEllipse(myb, pt.X, pt.Y, 20, 20);
g.DrawEllipse(p, pt.X, pt.Y, 20, 20);
}
}
In your code, you were just overwriting the circle in the same location for every Point in the points list.
Also, as Reza mentioned in the comments, you don't need to attach the PaintEventHandler event hanlder every time the PictureBox is clicked, you just need to do it once.
So I got to thinking, and then Visual Studio-ing, that perhaps we don't even need the foreach loop. I still maintain a List so we know where the user has clicked, but there's no need to loop through it and redraw everything every time.
I realize this doesn't handle the case where the underlying list is modified, but nor does the original sample. Here's my entire Form1 class:
public partial class Form1 : Form
{
private const int CircleDiameter = 20;
private const int PenWidth = 2;
private readonly List<Point> _points = new List<Point>();
public Form1()
{
InitializeComponent();
pictureBox1.Paint += (sender, args) =>
{
_points.ForEach(p => DrawPoint(p, args.Graphics));
};
}
private void pictureBox1_DoubleClick(object sender, EventArgs e)
{
var cursorLocation = pictureBox1.PointToClient(Cursor.Position);
_points.Add(cursorLocation);
var circleArea = new Rectangle(
cursorLocation.X - CircleDiameter/2 - PenWidth,
cursorLocation.Y - CircleDiameter/2 - PenWidth,
CircleDiameter + PenWidth*2,
CircleDiameter + PenWidth*2);
pictureBox1.Invalidate(circleArea);
}
private static void DrawPoint(Point point, Graphics graphics)
{
point.X -= CircleDiameter / 2;
point.Y -= CircleDiameter / 2;
using (var pen = new Pen(Color.Tomato, PenWidth))
using (var brush = new SolidBrush(Color.White))
{
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.DrawEllipse(pen, point.X, point.Y, CircleDiameter, CircleDiameter);
graphics.FillEllipse(brush, point.X, point.Y, CircleDiameter, CircleDiameter);
}
}
}
Update 1:
So I updated the code to use the Paint event which has the foreach loop. However, I don't Invalidate (and Paint) every time a circle is added - there's no need for that. Just adding a circle by drawing means the control only invalidates and re-paints the region where the new circle was added.
Try setting a breakpoint on the DrawAllPoints method. You'll see it only happens during full invalidation operations such as minimizing and restoring.
Update 2:
After further chat, I agree the Invalidate method is superior. Code updated to use Invalidate with a rectangle to invalidate.
And now it's looking very much like the OP :)
I recently started "learning" C#. Currently i am doing a some sort of a game for school project. I want to draw a circle on a form. I added a time every new circle is drawn every 1000 ms on a random place in form. But when i start my form nothing really happens.
namespace Vezba_4
{
public partial class Form1 : Form
{
// attempt is when you try to "poke" the circle
bool attempt = false;
int xc, yc, Br = 0, Brkr = 0;
Random R = new Random();
public Form1()
{
InitializeComponent();
timer1.Start();
}
// Br is the number of circles that player has successfully "poked". And Brkr is a total number of circles that have appeared on the game screen. the
private void timer1_Tick(object sender, EventArgs e)
{
Refresh();
SolidBrush cetka = new SolidBrush(Color.Red);
Graphics g = CreateGraphics();
xc = R.Next(15, ClientRectangle.Width - 15);
yc = R.Next(15, ClientRectangle.Height - 15);
g.FillEllipse(cetka, xc - 15, yc - 15, 30, 30);
Brkr++;
Text = Br + "FROM" + Brkr;
attempt = false;
g.Dispose();
cetka.Dispose();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (attempt == false)
{
if ((e.X - xc) * (e.X - xc) + (e.Y - yc) * (e.Y - yc) <= 225) Br++;
Text = Br + " FROM " + Brkr++;
}
attempt = true;
}
What is in the InitializeComponent method generated in the designer Form1.Designer.cs?
Is the event handler for the timer tick there?
//
// timer1
//
this.timer1.Interval = 1000;
this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
Edit:
For the mousedown would have to confirm that the handler is there in Form.Designer.cs as well:
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);
Here's the properties of the timer I used, I dont have a timer.Start() anyway, it's all just your code with the Enabled property set to true, and the interval 1000. [I noticed when I copied nothing happened, but when I set Enabled to true it started to appear.
You should draw your circle in Form1_Paint. Because it will be drawn only when Paint event fired, and when fired, it looks for Form1_Paint.
public partial class Form1 : Form
{
bool attempt = false;
int xc, yc, Br = 0, Brkr = 0;
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (attempt == false)
{
if ((e.X - xc) * (e.X - xc) + (e.Y - yc) * (e.Y - yc) <= 225) Br++;
Text = Br + " FROM " + Brkr++;
}
attempt = true;
}
public void Paaint()
{
SolidBrush cetka = new SolidBrush(Color.Red);
Graphics g = CreateGraphics();
xc = R.Next(15, ClientRectangle.Width - 15);
yc = R.Next(15, ClientRectangle.Height - 15);
g.FillEllipse(cetka, xc - 15, yc - 15, 30, 30);
Brkr++;
label1.Text = Br + "FROM" + Brkr;
attempt = false;
g.Dispose();
cetka.Dispose();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Paaint();
}
private void timer1_Tick(object sender, EventArgs e)
{
Invalidate();
}
Random R = new Random();
public Form1()
{
InitializeComponent();
}
}
Using below code I am able to draw arrow shaped button(shown below) ,but I want to draw hexagone(shown below as result image) ,so that I can use png image of size 175x154 as button image ,What Points I need to use to draw this ? and i need to to draw 6 such buttons ,how do i achieve this ?
private void Parent_Load(object sender, EventArgs e)
{
// Define the points in the polygonal path.
Point[] pts = {
new Point( 20, 60),
new Point(140, 60),
new Point(140, 20),
new Point(220, 100),
new Point(140, 180),
new Point(140, 140),
new Point( 20, 140)
};
// Make the GraphicsPath.
GraphicsPath polygon_path = new GraphicsPath(FillMode.Winding);
polygon_path.AddPolygon(pts);
// Convert the GraphicsPath into a Region.
Region polygon_region = new Region(polygon_path);
// Constrain the button to the region.
btnExam.Region = polygon_region;
// Make the button big enough to hold the whole region.
btnExam.SetBounds(
btnExam.Location.X,
btnExam.Location.Y,
pts[3].X + 5, pts[4].Y + 5);
}
The input should be a Rectangle which contains the Hexagonal shape, from that input we will calculate the Points for your Hexagonal shape, something like this:
public Point[] GetPoints(Rectangle container){
Point[] points = new Point[6];
int half = container.Height / 2;
int quart = container.Width/4;
points[0] = new Point(container.Left + quart, container.Top);
points[1] = new Point(container.Right - quart, container.Top);
points[2] = new Point(container.Right, container.Top + half);
points[3] = new Point(container.Right - quart, container.Bottom);
points[4] = new Point(container.Left + quart, container.Bottom);
points[5] = new Point(container.Left, container.Top + half);
return points;
}
private void Parent_Load(object sender, EventArgs e) {
//This should be placed first
// Make the button big enough to hold the whole region.
btnExam.SetBounds( btnExam.Location.X, btnExam.Location.Y, 100, 100);
// Make the GraphicsPath.
GraphicsPath polygon_path = new GraphicsPath(FillMode.Winding);
polygon_path.AddPolygon(GetPoints(btnExam.ClientRectangle));
// Convert the GraphicsPath into a Region.
Region polygon_region = new Region(polygon_path);
// Constrain the button to the region.
btnExam.Region = polygon_region;
}
You should update the Region whenever your btnExam's Size changes, so you should define some method called UpdateRegion and call it in a SizeChanged event handler:
private void UpdateRegion(){
GraphicsPath polygon_path = new GraphicsPath(FillMode.Winding);
polygon_path.AddPolygon(GetPoints(btnExam.ClientRectangle));
btnExam.Region = new Region(polygon_path);
}
//SizeChanged event handler for your btnExam
private void btnExam_SizeChanged(object sender, EventArgs e){
UpdateRegion();
}
//Then you just need to change the size of your btnExam in Parent_Load
private void Parent_Load(object sender, EventArgs e) {
//The button should be square
btnExam.SetBounds( btnExam.Location.X, btnExam.Location.Y, 100, 100);
}
Is this what you mean?
var xDisp = 10;
var yDisp = 10;
var length = 10;
var ls32 = (int)(length * Math.Sqrt(3) / 2.0);
var half = (int)(length / 2.0);
var points = new[]
{
new Point(xDisp + length, yDisp),
new Point(xDisp + half, yDisp + ls32),
new Point(xDisp - half, yDisp + ls32),
new Point(xDisp - length, yDisp),
new Point(xDisp - half, yDisp - ls32),
new Point(xDisp + half, yDisp - ls32)
};
I'm drawing a line in a picturebox this way:
horizontalstart = new Point(0, e.Y); //Start point of Horizontal line.
horizontalend = new Point(picbox_mpr.Width, e.Y); //End point of Horizontal line.
verticalstart = new Point(e.X, 0); //Start point of Vertical line
verticalend = new Point(e.X, picbox_mpr.Height); //End point of Vertical line.
Then on the paint event I do this:
e.Graphics.DrawLine(redline, horizontalstart, horizontalend); //Draw Horizontal line.
e.Graphics.DrawLine(redline, verticalstart, verticalend); //Draw Vertical line.
Pretty simple, now, my image can zoom and here's where I struggle..
How do I keep the line in the same spot that was drawn even if I zoom the image?
Instead of storing an absolute integer coordinate, store a decimal value representing the "percentage" of that coord with respect to the width/height of the image. So if the X value was 10 and the width was 100, you store 0.1. Let's say the image was zoomed and it was now 300 wide. The 0.1 would now translate to 0.1 * 300 = 30. You can store the "percentage" X,Y pairs in PointF() instead of Point().
Here's a quick example to play with:
public partial class Form1 : Form
{
private List<Tuple<PointF, PointF>> Points = new List<Tuple<PointF, PointF>>();
public Form1()
{
InitializeComponent();
this.Shown += new EventHandler(Form1_Shown);
this.pictureBox1.BackColor = Color.Red;
this.pictureBox1.SizeChanged += new EventHandler(pictureBox1_SizeChanged);
this.pictureBox1.Paint += new PaintEventHandler(pictureBox1_Paint);
}
void Form1_Shown(object sender, EventArgs e)
{
// convert absolute points:
Point ptStart = new Point(100, 25);
Point ptEnd = new Point(300, 75);
// to percentages:
PointF ptFstart = new PointF((float)ptStart.X / (float)pictureBox1.Width, (float)ptStart.Y / (float)pictureBox1.Height);
PointF ptFend = new PointF((float)ptEnd.X / (float)pictureBox1.Width, (float)ptEnd.Y / (float)pictureBox1.Height);
// add the percentage point to our list:
Points.Add(new Tuple<PointF, PointF>(ptFstart, ptFend));
pictureBox1.Refresh();
}
private void button1_Click(object sender, EventArgs e)
{
// increase the size of the picturebox
// watch how the line(s) change with the changed picturebox
pictureBox1.Size = new Size(pictureBox1.Width + 50, pictureBox1.Height + 50);
}
void pictureBox1_SizeChanged(object sender, EventArgs e)
{
pictureBox1.Refresh();
}
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
foreach (Tuple<PointF, PointF> tup in Points)
{
// convert the percentages back to absolute coord based on the current size:
Point ptStart = new Point((int)(tup.Item1.X * pictureBox1.Width), (int)(tup.Item1.Y * pictureBox1.Height));
Point ptEnd = new Point((int)(tup.Item2.X * pictureBox1.Width), (int)(tup.Item2.Y * pictureBox1.Height));
e.Graphics.DrawLine(Pens.Black, ptStart, ptEnd);
}
}
}
I'm implementing an application which want to draw lines in the panel. But the panel must be auto scrolled as it size can be expand at run time. The panel paint method I have used is as below.When I run the program it draws lines, but when I scroll down the panel the lines get crashes.How can I avoid that?
private void panel1_Paint(object sender, PaintEventArgs e)
{
this.DoubleBuffered = true;
Pen P = new Pen(Color.Red);
for (int i = 0; i < 10; i++) {
e.Graphics.DrawLine(P, (new Point(i * 40, 0)), (new Point(i * 40, 60 * 40)));
}
for (int i = 0; i < 60; i++)
{
e.Graphics.DrawLine(P, (new Point(0, i *40)), (new Point(10 * 40, i * 40)));
}
}
I'll assume that "get crashes" doesn't actually mean that your code crashes. You'll need to offset the drawing by the scroll amount. That's easy to do:
private void panel1_Paint(object sender, PaintEventArgs e) {
e.Graphics.TranslateTransform(panel1.AutoScrollPosition.X, panel1.AutoScrollPosition.Y);
// etc
//...
}