C# - asynchronous drawing on a panel - c#

In my Winforms application I'm attempting to recreate the Monte Carlo Method to approximate PI. The form itself consists of a box in which the user provides the amount of points and a panel, on which I want to draw. For this example though, let's assume the amount is going to be constant.
private int Amount = 10000;
private int InCircle = 0, Points = 0;
private Pen myPen = new Pen(Color.White);
private void DrawingPanel_Paint(object sender, PaintEventArgs e)
{
int w = DrawingPanel.Width, h = DrawingPanel.Height;
e.Graphics.TranslateTransform(w / 2, h / 2);
//drawing the square and circle in which I will display the points
var rect = new Rectangle(-w / 2, -h / 2, w - 1, h - 5);
e.Graphics.DrawRectangle(myPen, rect);
e.Graphics.DrawEllipse(myPen, rect);
double PIE;
int X, Y;
var random = new Random();
for (int i = 0; i < Amount; i++)
{
X = random.Next(-(w / 2), (w / 2) + 1);
Y = random.Next(-(h / 2), (h / 2) + 1);
Points++;
if ((X * X) + (Y * Y) < (w / 2 * h / 2))
{
InCircle++;
e.Graphics.FillRectangle(Brushes.LimeGreen, X, Y, 1, 1);
}
else
{
e.Graphics.FillRectangle(Brushes.Cyan, X, Y, 1, 1);
}
//just so that the points appear with a tiny delay
Thread.Sleep(1);
}
PIE = 4 * ((double)InCircle/(double)Points);
}
And this works. The visualization is great.
However, now I would like to recreate this asynchronously, so that while this is being drawn in the background, the app is still responsible and the user can do something else, or even just move the window around.
Initially I made a second method that does the drawing, which I call from the Event Handler:
private double Calculate(PaintEventArgs e)
{
int w = DrawingPanel.Width, h = DrawingPanel.Height;
double PIE;
int X, Y;
var random = new Random();
for (int i = 0; i < Amount; i++)
{
X = random.Next(-(w / 2), (w / 2) + 1);
Y = random.Next(-(h / 2), (h / 2) + 1);
Points++;
if ((X * X) + (Y * Y) < (w / 2 * h / 2))
{
InCircle++;
e.Graphics.FillRectangle(Brushes.LimeGreen, X, Y, 1, 1);
}
else
{
e.Graphics.FillRectangle(Brushes.Cyan, X, Y, 1, 1);
}
Thread.Sleep(1);
}
PIE = 4 * ((double)InCircle/(double)Points);
return PIE;
}
private void DrawingPanel_Paint(object sender, PaintEventArgs e)
{
int w = DrawingPanel.Width, h = DrawingPanel.Height;
e.Graphics.TranslateTransform(w / 2, h / 2);
var rect = new Rectangle(-w / 2, -h / 2, w - 1, h - 5);
e.Graphics.DrawRectangle(myPen, rect);
e.Graphics.DrawEllipse(myPen, rect);
var result = Calculate(e);
}
And this worked fine as well. Until I made the event handler async.
private async void DrawingPanel_Paint(object sender, PaintEventArgs e) {...}
Now, when I try running the Calculate method, either through Task.Run, or when I change its return type to Task and start that, I get the error: "Parameter is not valid" in the following line:
e.Graphics.FillRectangle(Brushes.LimeGreen, X, Y, 1, 1);
Now the question, is it possible to draw on a panel asynchronously, so that other parts of the app are not locked? And if not, is there a way to recreate this algorithm using any other way (not necessarily a panel)? Cheers.

You must not draw in a form or control from another thread. In debugging mode, WinForms will raise an exception if you do.
The best approach would be to use a Timer component. On each tick of the timer do one step from your loop. You will have to, of course, move the look counter as a global variable.

The issues other posters raise are completely valid, but this is actually completely achievable if you alter strategy a little.
While you can't draw to a UI Graphics context on another thread, there is nothing stopping you drawing to a non-UI one. So what you could do is to have a buffer to which you draw:
private Image _buffer;
You then decide what your trigger event for beginning drawing is; let's assume here it's a button click:
private async void button1_Click(object sender, EventArgs e)
{
if (_buffer is null)
{
_buffer = new Bitmap(DrawingPanel.Width, DrawingPanel.Height);
}
timer1.Enabled = true;
await Task.Run(() => DrawToBuffer(_buffer));
timer1.Enabled = false;
DrawingPanel.Invalidate();
}
You'll see the timer there; you add a Timer to the form and set it to match the drawing refresh rate you want; so 25 frames/second would be 40ms. In the timer event, you simply invalidate the panel:
private void timer1_Tick(object sender, EventArgs e)
{
DrawingPanel.Invalidate();
}
Most of your code just moves as-is into the DrawToBuffer() method, and you grab the Graphics from the buffer instead of from the UI element:
private void DrawToBuffer(Image image)
{
using (Graphics graphics = Graphics.FromImage(image))
{
int w = image.Width;
int h = image.Height;
// Your code here
}
}
Now all you need is to change the panel paint event to copy from the buffer:
private void DrawingPanel_Paint(object sender, PaintEventArgs e)
{
if (!(_buffer is null))
{
e.Graphics.DrawImageUnscaled(_buffer, 0, 0);
}
}
Copying the buffer is super-fast; probably uses BitBlt() underneath.
A caveat is you now need to be a little more careful about UI changes. E.g. if it is possible to change the size of your DrawingPanel half-way through a buffer render, you need to cater for that. Also, you need to prevent 2 buffer updates happening simultaneously.
There might be other things you need to cater for; e.g. you might need to DrawImage() instead of DrawImageUnscaled(), etc. I'm not claiming the above code is perfect, just something to give you an idea to work with.

You cannot make it asynchronously, because all drawings have to be made on UI thread.
What you can do is to utilize Application.DoEvents() to signal UI that it can process pending messages:
for (int i = 0; i < Amount; i++)
{
// consider extracting it to a function and separate calculation\logic from drawing
X = random.Next(-(w / 2), (w / 2) + 1);
Y = random.Next(-(h / 2), (h / 2) + 1);
Points++;
if ((X * X) + (Y * Y) < (w / 2 * h / 2)) // and this too
{
InCircle++;
e.Graphics.FillRectangle(Brushes.LimeGreen, X, Y, 1, 1);
}
else
{
// just another one suggestion - you repeat your code, only color changes
e.Graphics.FillRectangle(Brushes.Cyan, X, Y, 1, 1);
}
Thread.Sleep(1); // do you need it? :)
Application.DoEvents(); //
}
It will make your application responsive during drawing.
Read more about Application.DoEvents() here:
Use of Application.DoEvents()

Related

C# - Need help creating program that simulates Prime Number behavior by drawing lines

I challenged myself to create a program to experiment with Prime Numbers, I already have an idea how to do it, but not the coding skills..
My plan is this:
First ill create a program in C# that makes straight lines that follow some rules:
rule 1: all lines have the same length.
rule 2: all lines are either horizontal or vertical (no diagonals).
rule 3: Every new line begins where the previous line has ended (that way all the lines are joined).
Now for the tricky part:
I would like to make a counter that goes up by 1 each time a new line is created (starting value is 1 I presume), and whenever it reaches a prime number, the lines change 'direction' and start 'going' to the left of the original direction.
The first 10 lines will look something like this:
|
__ |
| | |
|__ __|
Notice how it changed direction at the 2nd, 3rd, 5th and 7th line.
(starting from the inside ending at the outside)
This will create a long twisting line and it will be pretty cool to see what pattern it makes.
Thanks for helping!
This was a fun project to play with. Thanks for my morning entertainment!
I used SLaks code in this SO thread to get a list of Primes less than a specified number.
Here is some sample output:
After generating the Primes, I walk the list and store the lines in a GraphicsPath. Using the Graphics.GetBounds() method then allows us to appropriately scale and transform the Graphics in the Paint() event of our Panel so that the entire drawing can be seen:
public partial class Form1 : Form
{
private const int segmentLength = 10;
private GraphicsPath gpPrimes = null;
public Form1()
{
InitializeComponent();
nudLessThanMax.Minimum = 15;
nudLessThanMax.Maximum = 500000;
nudLessThanMax.Value = nudLessThanMax.Minimum;
pnlPrimes.Paint += PnlPrimes_Paint;
pnlPrimes.SizeChanged += PnlPrimes_SizeChanged;
}
private void PnlPrimes_SizeChanged(object sender, EventArgs e)
{
pnlPrimes.Invalidate();
}
private void PnlPrimes_Paint(object sender, PaintEventArgs e)
{
if (gpPrimes != null)
{
RectangleF rectF = gpPrimes.GetBounds();
float max = Math.Max(rectF.Width + (2 * segmentLength), rectF.Height + (2 * segmentLength));
e.Graphics.TranslateTransform(pnlPrimes.Width / 2, pnlPrimes.Height / 2);
e.Graphics.ScaleTransform((float)pnlPrimes.Width / max, (float)pnlPrimes.Height / max);
e.Graphics.TranslateTransform(-(rectF.Left + rectF.Width / 2), -(rectF.Top + rectF.Height / 2));
e.Graphics.DrawPath(Pens.Black, gpPrimes);
}
}
private void btnGraphPrimes_Click(object sender, EventArgs e)
{
btnGraphPrimes.Enabled = false;
backgroundWorker1.RunWorkerAsync((int)this.nudLessThanMax.Value);
}
private List<int> PrimesLessThan(int num) // SLaks: https://stackoverflow.com/a/1510186/2330053
{
return Enumerable.Range(0, (int)Math.Floor(2.52 * Math.Sqrt(num) / Math.Log(num))).Aggregate(
Enumerable.Range(2, num - 1).ToList(),
(result, index) =>
{
var bp = result[index]; var sqr = bp * bp;
result.RemoveAll(i => i >= sqr && i % bp == 0);
return result;
}
);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int diff;
int num = (int)e.Argument;
Point pt = new Point(0, 0);
Point pt2 = pt;
GraphicsPath gp = new GraphicsPath();
List<int> primes = PrimesLessThan(num);
for(int i = 1; i < primes.Count; i++)
{
diff = primes[i] - primes[i - 1];
switch(i % 4)
{
case 1: // up
pt2 = new Point(pt.X, pt.Y - (segmentLength * diff));
break;
case 2: // left
pt2 = new Point(pt.X - (segmentLength * diff), pt.Y);
break;
case 3: // down
pt2 = new Point(pt.X, pt.Y + (segmentLength * diff));
break;
case 0: // right
pt2 = new Point(pt.X + (segmentLength * diff), pt.Y);
break;
}
gp.AddLine(pt, pt2);
pt = pt2;
}
gpPrimes = gp;
e.Result = primes;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
lbPrimes.DataSource = (List<int>)e.Result;
pnlPrimes.Invalidate();
btnGraphPrimes.Enabled = true;
}
}
Once again I wish I had enough points to make this just a comment, but I wanted to make sure you knew ahead of time this will not result in a sprial all the way out. There are pairs of primes only 2 apart (n, n+2 both prime) which will make your snake do a 180 and cross over itself.
If you are OK with that, then look into the System.Drawing namespace, especially at the Graphics.Drawline method. Or have a look at a little code that will help give you the right idea. Just put a pictureBox in a Windows Form and this code will put a line on it. From there you can work on your pen colors, scale, finding your primes, etc.
Bitmap bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawLine(new Pen(Color.Red), 300, 100, 100, 100);
}
pictureBox1.Image = bmp;

Initiate panel with drawings

I am now drawing to a panel some dots to indicate a sort of dotted grid with 1% of margin of total panel width.
This is what I am doing now:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Pen my_pen = new Pen(Color.Gray);
int x,y;
int k = 1 ,t = 1;
int onePercentWidth = panel1.Width / 100;
for (y = onePercentWidth; y < panel1.Height-1; y += onePercentWidth)
{
for (x = onePercentWidth; x < panel1.Width-1; x += onePercentWidth)
{
e.Graphics.DrawEllipse(my_pen, x, y, 1, 1);
}
}
}
What is bothering me is that when the app starts I can see the dots being drawn on the panel. Even if it is very quick it still bothers me a lot.
Is it possible to draw the dots on the panel and load it directly drawn?
Thank you for the help
You could create a bitmap and draw it instead.
But before you do that: DrawEllipse is a little expensive. Use DrawLine with a Pen that has a dotted linestyle instead:
int onePercentWidth = panel1.ClientSize.Width / 100;
using (Pen my_pen = new Pen(Color.Gray, 1f))
{
my_pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
my_pen.DashPattern = new float[] { 1F, onePercentWidth -1 };
for (int y = onePercentWidth; y < panel1.ClientSize.Height - 1; y += onePercentWidth)
e.Graphics.DrawLine(my_pen, 0, y, panel1.ClientSize.Width, y);
}
Note that I am using using so I don't leak the Pen and ClientSize so I use only the inner width. Also note the exaplanation about the custom DashPattern on MSDN

print two images on single page c#

I want to print two images on single page.
I have tried below code, but it is printing all images on different pages.
public void PD_PrintPage(object sender, PrintPageEventArgs e)
{
float W = e.MarginBounds.Width;
float H = e.MarginBounds.Height;
for (; FileCounter >= 0; FileCounter--)
{
try
{
Bitmap Bmp = new Bitmap(BmpFiles[FileCounter]);
if (Bmp.Width / W < Bmp.Height / H)
W = Bmp.Width * H / Bmp.Height;
else
H = Bmp.Height * W / Bmp.Width;
e.Graphics.DrawImage(Bmp, 0, 0, W, H);
break;
}
catch
{
}
}
FileCounter -= 1;
if (FileCounter > 0)
{
e.HasMorePages = true;
}
else
{
FileCounter = BmpFiles.Length - 1;
}
}
this will print all images in different page
I want some functionality that will print one image ,leave some space and again prine other image in same page if space is remaining.
In your code you're printig just one image per page because you leave the loop with the break-statement at the end of try. Instead of using break without a condition you should leave the loop dynamically based on the decision if it is possible to print only one image (not nough space for second image) ore two images (you achieved what you wanted).
//for-loop for printing maximum two images as long as there are files to print
for (int i = 0; i < 2 && FileCounter >= 0; i++)
{
//here comes your printing code just indicated with the draw-call
e.Graphics.DrawImage(Bmp, 0, 0, W, H);
//after a image was printed decrement your filecounter
FileCounter --;
//after a image was drawn check if there is enough space for the next image
//if there is not enough space leave the loop with break
if(condition)
break;
}
At the moment I don't have enough reputation for commenting something on this page... so: never use 'goto' as "Sayka" proposes in his answer. That is really bad style & coding
The working of printDocument is like this:
First the program reaches the printPage function, reads all the code and at the end of the function, if there exists a line e.hasMorePages = true; , then the program re-enters the function from the begining and read again the codes to print it to the next page and continues until it reads a line e.hasMorepages = false .. So u dont have to put a loop inside the function. What you have to do is to make variables inside the class, and incre or decrement the variables to make a condition that satisfies e.hasMorePages = false after your printing job has finished..
public void PD_PrintPage(object sender, PrintPageEventArgs e)
{
float W = e.MarginBounds.Width;
// if you are calculating the whole page margin, then split it to carry 2 images
float H = e.MarginBounds.Height / 2;
// for space btwn images
H -= 5.0;
// First Image
try
{
Bitmap Bmp = new Bitmap(BmpFiles[FileCounter]);
if (Bmp.Width / W < Bmp.Height / H)
W = Bmp.Width * H / Bmp.Height;
else
H = Bmp.Height * W / Bmp.Width;
e.Graphics.DrawImage(Bmp, 0, 0, W, H);
break;
}
catch
{
}
FileCounter --;
if (FileCounter < 0) goto goDaddy;
//Second Img
try
{
Bitmap Bmp = new Bitmap(BmpFiles[FileCounter]);
if (Bmp.Width / W < Bmp.Height / H)
W = Bmp.Width * H / Bmp.Height;
else
H = Bmp.Height * W / Bmp.Width;
e.Graphics.DrawImage(Bmp, 0, H + 2.5, W, H);
break;
}
catch
{
}
FileCounter --;
goDaddy:;
e.HasMorePages = (FileCounter >= 0)
}
I haven't checked the code but just tryin to show you the concept..
I found this to work extremely well, doesn't lose any image quality I experienced using memory streams.
private void printBothGraphs_Click(object sender, EventArgs e)
{
PrintPreviewDialog custom = new PrintPreviewDialog();
custom.ClientSize = new Size(1000, 750);
System.Drawing.Printing.PrintDocument pd = new System.Drawing.Printing.PrintDocument();
pd.DefaultPageSettings.Color = true;
pd.PrintPage += new PrintPageEventHandler(pd_PrintPageBoth);
custom.Document = pd;
custom.ShowDialog();
}
private void pd_PrintPageBoth(object sender, PrintPageEventArgs ev)
{
// Create and initialize print font
System.Drawing.Font printFont = new System.Drawing.Font("Arial", 10);
ev.PageSettings.Color = true;
// Create Rectangle structure, used to set the position of the chart Rectangle
Rectangle myRec = new System.Drawing.Rectangle(ev.MarginBounds.X, ev.MarginBounds.Y, ev.MarginBounds.Width, ev.MarginBounds.Height / 2);
Rectangle myRec2 = new System.Drawing.Rectangle(ev.MarginBounds.X, ev.MarginBounds.Height / 2 + 90, ev.MarginBounds.Width, ev.MarginBounds.Height / 2);
//dataChart and outputGraph are two mscharts in my form
dataChart.Printing.PrintPaint(ev.Graphics, myRec);
outputGraph.Printing.PrintPaint(ev.Graphics, myRec2);
}

Draw types of signals with changing amplitude and frequency on-the-fly

As title said:
I have form with 2 trackbars. One for frequency and one for amplitude. I set up timer for on-the-fly changing.
private void timer1_Tick(object sender, EventArgs e)
{
float amplitude, frequency;
amplitude = Convert.ToSingle(trackBar1.Value) / 100;
label1.Text = amplitude.ToString() + " V";
frequency = trackBar2.Value;
label2.Text = frequency.ToString() + " Hz";
}
I have also 4 radio-buttons to decide, which type of signal will be displayed (sine, square, triangle, sawthoot)
Now I have this implemented with ImageList (change image of signal).
How can I draw type of signal and regulate it with with trackbars? So it will be like in osciloscope.
Thanks for your answers and code.
Lets start by creating the different signal types, this is a function that creates one wavelength of amplitude 1:
private PointF[] CreateBaseSignal(SignalType signalType)
{
switch (signalType)
{
case SignalType.Sine:
const int oversampling = 32;
PointF[] signal = new PointF[oversampling];
for (int i = 0; i < signal.Length; i++)
{
signal[i].X = (float) i / oversampling;
signal[i].Y = Convert.ToSingle(Math.Sin((double) i / oversampling * 2 * Math.PI));
}
return signal;
case SignalType.Square:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, -1.0f),
new PointF(0.5f, 1.0f),
new PointF(1.0f, 1.0f),
};
case SignalType.Triangle:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, 1.0f),
};
case SignalType.Sawtooth:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(1.0f, 1.0f),
};
default:
throw new ArgumentException("Invalid signal type", "signalType");
}
}
Then we create the actual signal with the selected amplitude and frequency:
private PointF[] CreateSignal(PointF[] baseSignal, float frequency, float amplitude)
{
PointF[] signal = new PointF[Convert.ToInt32(Math.Ceiling(baseSignal.Length * frequency))];
for(int i = 0; i < signal.Length; i++)
{
signal[i].X = baseSignal[i % baseSignal.Length].X / frequency + (i / baseSignal.Length) / frequency;
signal[i].Y = baseSignal[i % baseSignal.Length].Y * amplitude;
}
return signal;
}
Before attempting to plot this signal to a PictureBox, we scale the signal to fit the width and height:
private PointF[] ScaleSignal(PointF[] signal, int width, int height)
{
const float maximumAmplitude = 10.0f;
PointF[] scaledSignal = new PointF[signal.Length];
for(int i = 0; i < signal.Length; i++)
{
scaledSignal[i].X = signal[i].X * width;
scaledSignal[i].Y = signal[i].Y * height / 2 / maximumAmplitude;
}
return scaledSignal;
}
Using Graphics.DrawLine to plot the signal is way better than Bitmap.SetPixel, since the data points will be connected even at high frequencies. Bitmap.SetPixel is also very slow, you really need to use Bitmap.LockBits and unsafe code for manipulating single pixels to achieve any decent performance. Using Graphics.DrawLine, you also have control over line width, anti-aliasing etc.
Since we have stored the signal in a PointF array, we can use the simple Graphics.DrawLines method to plot the signal instead of iterating over the data points:
private void PlotSignal(PointF[] signal, PictureBox pictureBox)
{
Bitmap bmp = new Bitmap(pictureBox.ClientSize.Width, pictureBox.ClientSize.Height);
signal = ScaleSignal(signal, bmp.Width, bmp.Height); // Scale signal to fit image
using(Graphics gfx = Graphics.FromImage(bmp))
{
gfx.SmoothingMode = SmoothingMode.HighQuality;
gfx.TranslateTransform(0, bmp.Height / 2); // Move Y=0 to center of image
gfx.ScaleTransform(1, -1); // Make positive Y axis point upward
gfx.DrawLine(Pens.Black, 0, 0, bmp.Width, 0); // Draw zero axis
gfx.DrawLines(Pens.Blue, signal); // Draw signal
}
// Make sure the bitmap is disposed the next time around
Image old = pictureBox.Image;
pictureBox.Image = bmp;
if(old != null)
old.Dispose();
}
If you redraw the signal often, you probably want to reuse the the Bitmap and Graphics objects instead of creating new ones each time. Just remember to call Graphics.Clear between each redraw.
Putting everything together in one big statement:
PlotSignal(
CreateSignal(
CreateBaseSignal(signalType),
frequency,
amplitude),
thePictureBox);
If you're after a fast plotting library, I really like Dynamic Data Display
Dynamic Data Display
This is a WPF component, but for fast, smooth drawing applications I really think it is worthwhile to port to WPF sooner rathar than later. It feels like you're not too far into your project at the moment anyway.
Development for WPF seems to have stopped for this component (although it continues to be worked on for Silverlight). The documentation is terrible but the source code is available from the link above so you can extend it as needed (it's quite well written and very extensible) and the source is invaluable as a substitute for the near complete lack of any documentation.
Assuming you want to plot a sin wave on a picture box control, create a picture box control on your form then perform the following:
int width = pictureBox1.Width;
int height = pictureBox1.Height;
Bitmap b = new Bitmap(width, height);
for (int i = 0; i < width; i++)
{
int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);
b.SetPixel(i, y, System.Drawing.Color.Red);
}
pictureBox1.Image = b;

Slow adding of controls to a form

Why does the paint even take so long?
public Form1()
{
InitializeComponent();
SuspendLayout();
double scale = ClientSize.Width / 11;
for (int i = 1; i < 10; i++)
{
for (int j = 1; j < 10; j++)
{
everybox[i - 1, j - 1] = new TextBox
{
Location = new Point((int)(scale * i), (int)(scale * j)),
Size = new Size((int)scale - 2, (int)scale - 2),
Multiline = true
};
Controls.Add(everybox[i - 1, j - 1]);
}
}
ResumeLayout();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
float scale = ClientSize.Width / 11;
Graphics g = this.CreateGraphics();
int counter = 0;
for (float i = scale; i <= this.ClientSize.Width - scale; i += scale)
{
counter++;
if ((counter - 1) % 3 != 0)
{
g.DrawLine(new Pen(Color.Black), new Point((int)i, (int)scale),
new Point((int)i, ClientSize.Width - (int)scale));
g.DrawLine(new Pen(Color.Black), new Point((int)scale, (int)i),
new Point(ClientSize.Width - (int)scale, (int)i));
}
else
{
g.DrawLine(new Pen(Color.Black, 3f), new Point((int)i, (int)scale),
new Point((int)i, ClientSize.Width - (int)scale));
g.DrawLine(new Pen(Color.Black, 3f), new Point((int)scale, (int)i),
new Point(ClientSize.Width - (int)scale, (int)i));
}
}
}
It is rather annoying, and causes noticeable lag. everybox is a TextBox[9,9] object.
Per my comment, change:
Graphics g = this.CreateGraphics();
to
e.Graphics
Paint can definitely get called alot and if you are getting too many calls, it probably has nothing to do with this bit of code. One thing that would help the performance of this particular bit is to try reducing the amount of work you do...
Graphics g = e.Graphics;
Pen bp = new Pen(Color.Black, 3f);
Point start = new Point(0,0);
Point stop = new Point(0,0);
for (float i = scale; i <= this.ClientSize.Width - scale; i += scale)
{
int iAsInt = (int)i;
int scaleAsInt = (int)scale;
int w = ClientSize.Width;
counter++;
if ((counter - 1) % 3 != 0)
{
start.X = iAsInt;
start.Y = scaleAsInt;
stop.X = iAsInt;
stop.Y = w-scaleAsInt;
g.DrawLine(Pens.Black, start, stop);
start.X = scaleAsInt;
start.Y = iAsInt;
stop.X = w-scaleAsInt;
stop.Y = iAsInt;
g.DrawLine(Pens.Black, start, stop);
// Note: this looks like more work, but it is actually less
// your code still has to make all the assignments in addition to
// newing up the points (and later having to garbage collect them)
}
else
{
// TODO: reuse the start/stop points here
g.DrawLine(bp, new Point(iAsInt, scaleAsInt), new Point(iAsInt, w - scaleAsInt);
g.DrawLine(bp, new Point(scaleAsInt, iAsInt), new Point(w - scaleAsInt, iAsInt));
}
}
To specifically stop the overdrawing of your lines, look at the ClipRectangle member of PaintEventArgs. If part of your line falls within the area of the clip rectangle, redraw it.
A possible reason is because you are trying to draw too many heavy-weight components. If my math is correct you are redrawing 9 * 9 * 9 * 9 = 6561 objects. WinForms are not designed to support redrawing of that many components in the efficient way.
You may need to think if you really need to use that many heavy-weight graphic components with WinForms. There might be lighter components or you can switch to XNA (which has camera, views etc - all of that reduce the number of objects needed to be redrawn) or WPF depending on the context.
The code you posted causes the paint event to fire 81 times (9*9). Once for each control being added to the form. Any more times are due to something that invalidates the form, like the mouse moving over it, another window moving over it, or the form resizing. Some code you aren't showing us may be responsible.

Categories