C# - WInForms - Dispose of old graphics at redrawn event - c#

I'm following a tutorial to get the basics of dealing with a paint event using windows forms.
So far the program kind of works, but any update of the graphics is not deleting the previously drawn lines (the graphics is not being disposed of).
The original tutorial used Refresh, but that didn't seem to work and I replaced it with Invalidate+Update.
Also, setting the graphics control to this.CreateGraphics() wasn't working and I switched it to panel2.CreateGraphics() (I also tried e.Graphics without results).
namespace GraphicsTutorialV1
{
public partial class Form1 : Form
{
Pen myPen = new Pen(Color.Black);
Graphics g = null;
static int start_x, start_y;
static int end_x, end_y;
static int my_angle = 0;
static int my_length = 0;
static int my_increment = 0;
static int num_lines = 0;
public Form1()
{
InitializeComponent();
Int32.TryParse(textBox1.Text, out num_lines);
Int32.TryParse(textBox2.Text, out my_angle);
Int32.TryParse(textBox3.Text, out my_length);
Int32.TryParse(textBox4.Text, out my_increment);
start_x = (panel2.Width / 2);
start_y = (panel2.Height / 2);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
myPen.Width = 1;
g = panel2.CreateGraphics();
//g = e.Graphics;
for(int i = 0; i < num_lines; i++)
{
drawLine();
}
}
private void drawLine()
{
int temp;
Int32.TryParse(textBox2.Text, out temp);
my_angle = my_angle + temp;
Int32.TryParse(textBox4.Text, out temp);
my_length = my_length + temp;
end_x = (int)(start_x + Math.Cos(my_angle * Math.PI / 180) * my_length);
end_y = (int)(start_y + Math.Sin(my_angle * Math.PI / 180) * my_length);
Point[] points =
{
new Point(start_x, start_y),
new Point(end_x, end_y)
};
start_x = end_x;
start_y = end_y;
g.DrawLines(myPen, points);
}
private void button1_Click(object sender, EventArgs e)
{
Int32.TryParse(textBox1.Text, out num_lines);
Int32.TryParse(textBox2.Text, out my_angle);
Int32.TryParse(textBox3.Text, out my_length);
Int32.TryParse(textBox4.Text, out my_increment);
this.Invalidate();
this.Update();
}
}
}

The problem with my code was that the drawing instructions were included in the paint event for the form. By setting the drawing in the paint event for the panel and then setting the graphics to the standard paint event for it everything worked out. Also, Refresh started to work.

Related

Windows Forms Bitmap vs. Drawing Speed

I have data in a multidimensional array of doubles and I want to draw it on my form in a grid (with different values being different colors). Write now I am using a Panel control and in its OnPaint() I do do the drawing by calling Graphics.DrawRectangle() for each entry in the array. The data changes very frequently, so I call Refresh() on the panel when it does, but this is quite slow and I get a lot of flickering, I can also see the drawing of each rectangle happen sequentially, and I know that parallel drawing of the rectangles is not possible, because it is not thread-safe.
Would constructing a Bitmap where each pixel is an entry in the array, and then setting a PictureBox to show the Bitmap (and scale its size up) be faster than using the panel and OnPaint?
Here is a sample of the code I am using:
namespace PredPreySim
{
public partial class SimulationForm : Form
{
Simulation simulation; // instance of the simulation class
bool running = true;
int simWidth = 30;
int simHeight = 20;
int cellSize = 15;
System.Threading.Thread t;
public SimulationForm()
{
InitializeComponent();
simulation = new Simulation(simWidth, simHeight, numHerbivores, 0.3);
graphicsTimeDelay = Convert.ToInt32(GraphicsTimeDelay.Value);
ResizePanel();
}
private void ResizePanel()
{
panel1.Width = simWidth * cellSize;
panel1.Height = simHeight * cellSize;
}
// draws the board
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = e.Graphics;
SolidBrush brush = new SolidBrush(Color.Green);
RectangleF rectangle = new Rectangle();
rectangle.Width = cellSize;
rectangle.Height = cellSize;
// draw all the plants
Color plantColor;
for (int i = 0; i < simWidth; ++i)
{
for (int j = 0; j < simHeight; ++j)
{
int r, g = 255, b;
r = b = 255 - (int)(255.0 * Math.Tanh(simulation.plants[i, j]));
plantColor = Color.FromArgb(100, r, g, b);
brush.Color = plantColor;
rectangle.Location = new PointF(i * cellSize, j * cellSize);
graphics.FillRectangle(brush, rectangle);
}
}
brush.Dispose();
graphics.Dispose();
}
private void StartStopButton_Click(object sender, EventArgs e)
{
if (running) // then stop
{
running = false;
StartStopButton.Text = "Start";
}
else // start
{
running = true;
StartStopButton.Text = "Stop";
t = new Thread(new ThreadStart(this.runSimulation));
t.Start();
t.Priority = System.Threading.ThreadPriority.Highest;
}
}
private void runSimulation()
{
while (running)
{
simulation.Update();
panel1.Invoke(new MethodInvoker(Refresh)); // makes the call thread-safe
}
t.Abort();
}
}
}

Timer start event

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();
}
}

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();
}

Send drawing from a class to a form in C#

I have a class named CircleSector which draws a PIChart.
I am unable to draw the PIChart if I call the Class from form1.
Here is my code:
Form1:
public void button1_Click(object sender, EventArgs e)
{
int textdata = Convert.ToInt32(textBox1.Text);
CS = new CircleSector(textdata, this);
// CS.GetGraphicSector(this);
}
CircleSector:
public CircleSector(int TextData , Form1 D)
{
Pen CirclePen = new Pen(Color.Black);
Rect = new Rectangle(XAxis, YAxis, CircleRadius, CircleRadius);
float temp1 = 0;
SectorCircle = this.CreateGraphics();
PIVal = - 360 / TextData;
float temp2 = PIVal;
for (int i = 0; i <= TextData; i++)
{
m_Value = i;
SectorCircle.DrawPie(CirclePen, Rect, 0, temp2);
temp1 = temp2;
temp2 = temp2 - PIVal;
}
// MessageBox.Show("Mouse Pressed");
// return SectorCircle;
}
I think the problem is here.
SectorCircle = this.CreateGraphics();
try this.
SectorCircle = D.CreateGraphics();

Draw a line on PictureBox from parent

I have a PictureBox as UserControl. I added this User Control on the main form. Now I have to press a button and create a line on the user control. On my project, every time I press this button, I want to send to user control parameters of two PointF(x and y) and draw a new line, in addition to the existent one. I have so far the Paint event when picturebox is loaded.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Pen graphPen = new Pen(Color.Red, 2);
PointF pt1D = new PointF();
PointF pt2D = new PointF();
pt1D.X = 0;
pt1D.Y = 10;
pt2D.X = 10;
pt2D.Y = 10;
e.Graphics.DrawLine(graphPen, pt1D, pt2D);
}
Assuming that you want to draw the line on the click of the button, here's a modified version of your code:
List<PointF> points = new List<PointF>();
Pen graphPen = new Pen(Color.Red, 2);
private void btnDrawLines_Click(object sender, EventArgs e)
{
Graphics g = picBox.CreateGraphics();
PointF pt1D = new PointF();
PointF pt2D = new PointF();
pt1D.X = 0;
pt1D.Y = 10;
pt2D.X = 10;
pt2D.Y = 10;
g.DrawLine(graphPen, pt1D, pt2D);
points.Add(pt1D);
points.Add(pt2D);
}
private void picBox_Paint(object sender, PaintEventArgs e)
{
for (int i = 0; i < points.Count; i+=2)
e.Graphics.DrawLine(graphPen, points[i], points[i + 1]);
}
Note that you can get a Graphics object through the PictureBox class's CreateGraphics() method which is the same as the e.Graphics object in the Paint event handler.
If you are adding lines to be drawn, the you probably want a little Line class:
public class Line {
public Point Point1 { get; set; }
public Point Point2 { get; set; }
public Line(Point point1, Point point2) {
this.Point1 = point1;
this.Point2 = point2;
}
}
And then you can just add these "lines" to a list:
private List<Line> _Lines = new List<Line>();
and add to them and tell the control to update it's drawing:
_Lines.Add(new Line(new Point(10, 10), new Point(42, 42)));
_Lines.Add(new Line(new Point(20, 40), new Point(20, 60)));
pictureBox1.Invalidate()
then in your drawing:
private void pictureBox1_Paint(object sender, PaintEventArgs e) {
e.Graphics.Clear(Color.White);
foreach (Line l in _Lines) {
e.Graphics.DrawLine(Pens.Red, l.Point1, l.Point2);
}
}

Categories

Resources