Windows Forms Bitmap vs. Drawing Speed - c#

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

Related

Resize rectangle with form Size in real time?

Here is the following rectangle below:
When I resize the form, I need this rectangle to match the size of the form.
When changing the width of the rectangle, do not interfere with its visibility within the form.
I'm using the following:
Note:
I did the rectangle manually, but if you have rectangle ready, better yet!
public Form1()
{
InitializeComponent();
this.Paint += Form1_Paint;
this.rectangles = new Dictionary<string, Rectangle>();
this.sizeScreen = this.Size;
this.sizeRectangles = new Size(8, 8);
this.brush = new SolidBrush(Color.Red);
FillLeft();
FillRight();
FillUp();
FillDown();
}
private Size sizeScreen;
private Size sizeRectangles;
private SolidBrush brush;
private Dictionary<string, Rectangle> rectangles;
private void FillLeft()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeScreen.Height,
Width = this.sizeRectangles.Width,
X = 0,
Y = this.sizeRectangles.Height
};
this.rectangles.Add("left", rectangle);
}
private void FillRight()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeScreen.Height,
Width = this.sizeRectangles.Width,
X = this.sizeScreen.Width - (this.sizeRectangles.Width * 5),
Y = this.sizeRectangles.Height
};
this.rectangles.Add("right", rectangle);
}
private void FillUp()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeRectangles.Height,
Width = this.sizeScreen.Width,
X = 0,
Y = this.sizeRectangles.Height
};
this.rectangles.Add("up", rectangle);
}
private void FillDown()
{
Rectangle rectangle = new Rectangle()
{
Height = this.sizeRectangles.Height,
Width = this.sizeScreen.Width,
X = 0,
Y = this.sizeScreen.Height - (this.sizeRectangles.Height * 11)
};
this.rectangles.Add("down", rectangle);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
for (int i = 0; i < this.rectangles.Count; i++)
{
e.Graphics.FillRectangles(this.brush, this.rectangles.Values.ToArray());
}
}
I want to set the rectangle on the form when it is resized
This is the way I'm creating the rectangle, but it does not stay right on the screen, to resize it I do not know
I think this would simplify what you are trying to do:
const int PenWidth = 10;
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle r = this.ClientRectangle;
Pen pen = new Pen(Color.Red, PenWidth);
e.Graphics.DrawRectangle(pen, r);
}
You could even add a margin:
const int PenWidth = 10;
const int PenMargin = 10;
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle r = this.ClientRectangle;
r.Inflate(-PenMargin, -PenMargin);
Pen pen = new Pen(Color.Red, PenWidth);
e.Graphics.DrawRectangle(pen, r);
}
To prevent traces (suggested by Wyck):
private void Form1_Resize(object sender, EventArgs e)
{
Invalidate();
}
Handle the Resize event and call Invalidate in the handler. Create a Pen of the desired color and width and set its Alignment to Inset. Handle the Paint event and in the handler call DrawRectangle passing in the ClientRectangle of the form.
Here is an example.
const float borderWidth = 8.0f;
Pen borderPen = new Pen(Color.Red, borderWidth) { Alignment = System.Drawing.Drawing2D.PenAlignment.Inset };
public Form2()
{
InitializeComponent();
this.Paint += Form2_Paint;
this.Resize += Form2_Resize;
}
private void Form2_Resize(object sender, EventArgs e)
{
Invalidate();
}
private void Form2_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawRectangle(borderPen, this.ClientRectangle);
}
Apply the following fixes to the code:
Set ResizeRedraw property of the form to true. It sets the underlying style for the form so by each resize it sends the paint message and you don't need to handle Resize event.
Use DrawRectangle and draw using wide pen. So you don't need to fill multiple rectangles.
Set the PenAlignment to Inset. So you don't need to calculate the location of rectangle.
Do dispose the pen when you don't need it.
Example
public Form1()
{
InitializeComponent();
ResizeRedraw = true;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (var pen = new Pen(Color.Red, PenWidth))
{
pen.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
e.Graphics.DrawRectangle(pen, ClientRectangle);
}
}
i have used this on my current project. when ever you resize the form, it will automatically resize all the object inside your form
i have class named clsResize and i call this on the form load.
1st you have to initialize the class inside the form then create 2 new method.
see example below
public partial class frmNewForm : Form
{
clsResize _form_resize;
public string selectedProd;
public frmNewForm()
{
InitializeComponent();
_form_resize = new clsResize(this);
this.Load += _Load;
this.Resize += _Resize;
}
private void _Load(object sender, EventArgs e)
{
_form_resize._get_initial_size();
}
private void _Resize(object sender, EventArgs e)
{
_form_resize._resize();
}
}
and here is the class that i used.
public class clsResize
{
List<System.Drawing.Rectangle> _arr_control_storage = new List<System.Drawing.Rectangle>();
private bool showRowHeader = false;
public clsResize(Form _form_)
{
form = _form_; //the calling form
_formSize = _form_.ClientSize; //Save initial form size
_fontsize = _form_.Font.Size; //Font size
}
private float _fontsize { get; set; }
private System.Drawing.SizeF _formSize {get;set; }
private Form form { get; set; }
public void _get_initial_size() //get initial size//
{
var _controls = _get_all_controls(form);//call the enumerator
foreach (Control control in _controls) //Loop through the controls
{
_arr_control_storage.Add(control.Bounds); //saves control bounds/dimension
//If you have datagridview
if (control.GetType() == typeof(DataGridView))
_dgv_Column_Adjust(((DataGridView)control), showRowHeader);
}
}
public void _resize() //Set the resize
{
double _form_ratio_width = (double)form.ClientSize.Width /(double)_formSize.Width; //ratio could be greater or less than 1
double _form_ratio_height = (double)form.ClientSize.Height / (double)_formSize.Height; // this one too
var _controls = _get_all_controls(form); //reenumerate the control collection
int _pos = -1;//do not change this value unless you know what you are doing
try
{
foreach (Control control in _controls)
{
// do some math calc
_pos += 1;//increment by 1;
System.Drawing.Size _controlSize = new System.Drawing.Size((int)(_arr_control_storage[_pos].Width * _form_ratio_width),
(int)(_arr_control_storage[_pos].Height * _form_ratio_height)); //use for sizing
System.Drawing.Point _controlposition = new System.Drawing.Point((int)
(_arr_control_storage[_pos].X * _form_ratio_width), (int)(_arr_control_storage[_pos].Y * _form_ratio_height));//use for location
//set bounds
control.Bounds = new System.Drawing.Rectangle(_controlposition, _controlSize); //Put together
//Assuming you have a datagridview inside a form()
//if you want to show the row header, replace the false statement of
//showRowHeader on top/public declaration to true;
if (control.GetType() == typeof(DataGridView))
_dgv_Column_Adjust(((DataGridView)control), showRowHeader);
//Font AutoSize
control.Font = new System.Drawing.Font(form.Font.FontFamily,
(float)(((Convert.ToDouble(_fontsize) * _form_ratio_width) / 2) +
((Convert.ToDouble(_fontsize) * _form_ratio_height) / 2)));
}
}
catch(Exception e)
{
MessageBox.Show(e.Message);
return;
}
}
private void _dgv_Column_Adjust(DataGridView dgv, bool _showRowHeader) //if you have Datagridview
//and want to resize the column base on its dimension.
{
int intRowHeader = 0;
const int Hscrollbarwidth = 5;
if (_showRowHeader)
intRowHeader = dgv.RowHeadersWidth;
else
dgv.RowHeadersVisible = false;
for (int i = 0; i < dgv.ColumnCount; i++)
{
if (dgv.Dock == DockStyle.Fill) //in case the datagridview is docked
dgv.Columns[i].Width = ((dgv.Width - intRowHeader) / dgv.ColumnCount);
else
dgv.Columns[i].Width = ((dgv.Width - intRowHeader - Hscrollbarwidth) / dgv.ColumnCount);
}
}
private static IEnumerable<Control> _get_all_controls(Control c)
{
return c.Controls.Cast<Control>().SelectMany(item =>
_get_all_controls(item)).Concat(c.Controls.Cast<Control>()).Where(control =>
control.Name != string.Empty);
}
}

Attempting to draw rectangles multithreaded

I am attempting to draw about 3600 points on a form, it is pretty slow using one thread so I decided I want to use 4 threads for it.
In my code I divide the 3600 points to the 4 threads and they are supposed to draw it. however for some reason an ArgumentOutOfRangeException is being thrown.
I tried to debug my code but I couldn't find the mistake.
here is the code :
(Ignore the class _3DPoint, it is just a point that has x,y,z values. when I draw them I only use the x,y values.)
code for drawing the points :
public Graphics g; //g = this.CreateGraphics() in form1.Load()
public void drawrectangle(_3DPoint)
float xCord = float.Parse(p.x.ToString());
float yCord = float.Parse(p.y.ToString());
Brush b = new SolidBrush(Color.White);
xCord = lsize * xCord + center.X;
yCord = lsize * yCord + 10 + center.Y;
g.FillRectangle(b, xCord, yCord, 2, 2);
}
lsize, center are just variables for aligning the points as I want them.
All of the multithread action code:
public List<_3DPoint[]> multiThreadsdata = new List<_3DPoint[]>();
public void handlemultithread(_3DPoint[] P)
{
g.Clear(Color.Black);
for (int i = 0; i < multiThreads.Length; i++)
{
multiThreadsdata.Add(new _3DPoint[P.Length / multiThreads.Length]);
}
for (int i = 0; i < multiThreads.Length; i++)
{
for (int j = (P.Length / multiThreads.Length) * (i); j < (P.Length / multiThreads.Length) * (i + 1); j++)
{
multiThreadsdata[i][j - ((P.Length / multiThreads.Length) * i)] = new _3DPoint(P[j]);
}
}
for (int i = 0; i < multiThreads.Length; i++)
{
multiThreads[i] = new Thread(() => drawPoints(multiThreadsdata[i]));
multiThreads[i].Start();
}
}
delegate void SetCallBackPoint(_3DPoint location);
public void drawPoints(_3DPoint[] locations)
{
for (int i = 0; i < locations.Length; i++)
{
if (this.InvokeRequired)
{
SetCallBackPoint e = new SetCallBackPoint(drawrectangle);
this.Invoke(e, new object[] { locations[i] });
}
else
{
drawrectangle(locations[i]);
}
}
}
P is a _3DPoint array that contains all the 3600 points.
mutliThreads is a Thread[] containing 4 threads.
I get the exception in handlemultithread method. in the third line of this for loop :
for (int i = 0; i < multiThreads.Length; i++)
{
multiThreads[i] = new Thread(() => drawPoints(multiThreadsdata[i])); // <- here.
multiThreads[i].Start();
}
I don't know what is the problem, my guess is that there is some problem with the multithreading because I'm just a beginner with multithreading.
Thanks a bunch.
It is entirely possible to Draw 3600 rectangles quickly on a form when you apply the suggestions in the comments.
If that doesn't give you enough time you can consider creating Images on a single background thread, store them in some sort of buffer until they are needed to e painted on the Graphics object of the Paint event of the form. That is only feasible if you can know upfront what needs to be painted on the next frame.
This example uses a simple Background worker to fill an ConcurrentQueue with images. The comments in the code explain what is going on.
public partial class Form1 : Form
{
static ConcurrentQueue<Image> buffer = new ConcurrentQueue<Image>();
static Random r = new Random();
public Form1()
{
InitializeComponent();
backgroundWorker1.RunWorkerAsync();
// this is already a great performance win ...
DoubleBuffered = true;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Image img =null;
// get from buffer ..
if (!buffer.TryDequeue(out img))
{
// nothing available
// direct random
for (var x = 0; x < e.ClipRectangle.Width; x++)
{
for (var y = 0; y < e.ClipRectangle.Height; y++)
{
using (var pen = new Pen(new SolidBrush(Color.FromArgb(r.Next(255), r.Next(255), r.Next(255)))))
{
e.Graphics.DrawRectangle(pen, x, y, 1, 1);
}
}
}
}
else
{
// otherwise Draw the prepared image
e.Graphics.DrawImage(img,0,0);
Trace.WriteLine(buffer.Count);
img.Dispose();
}
}
private void button1_Click(object sender, EventArgs e)
{
// force a repaint of the Form
Invalidate();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// as long as the form is not disposed
while (!IsDisposed)
{
// we keep 60 images in memory
if (buffer.Count < 60)
{
// bitmap
var bmp = new Bitmap(this.Width, this.Height);
var img = Graphics.FromImage(bmp);
// draw
for (int i = 0; i < 3600; i++)
{
using (var pen = new Pen(new SolidBrush(Color.FromArgb(r.Next(255), r.Next(255), r.Next(255)))))
{
img.DrawRectangle(pen, r.Next(Width),r.Next(Height), r.Next(Width), r.Next(Height));
}
}
// store the drawing in the buffer
buffer.Enqueue(bmp);
}
else
{
// simple and naive way to give other threads a bit of room
Thread.Sleep(0);
}
}
}
}
Keep in mind that when you have a CPU heavy process adding more threads will not by magic make your methods run quicker. You might even make it worse: more threads compete for time on the CPU.

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

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.

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

Drawing Multiple Rectangles c#

What would be the best way to draw 25 rectangles (5*5) in c#?
I later need to be able to reach a specific rectangle and change its color, for instance change the color to red if the user inputs the incorrect word.
Would it be more suitable to create an array of rectangles in this case?
This is what i have so far
Graphics g = pictureBox1.CreateGraphics();
int x =0;
int y= 0;
int width = 20;
int height = 20;
for (int i = 0; i < 25; i++)
{
if (i <= 4)
{
g.FillRectangle(Brushes.Blue, x, y, width, height);
x += 50;
}
else if (i > 4)
{
y = 50;
g.FillRectangle(Brushes.Blue, x, y, width, height);
x += 50;
}
}
This should get you started, not the complete code. You will need to add a PictureBox control and use the default name (picurebox1). EDIT: Need to add a button too :)
public partial class Form1 : Form
{
public List<Rectangle> listRec = new List<Rectangle>();
Graphics g;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Rectangle rect = new Rectangle();
rect.Size = new Size(100,20);
for (int x = 0; x < 5; x++)
{
rect.X = x * rect.Width;
for (int y = 0; y < 5; y++)
{
rect.Y = y * rect.Height;
listRec.Add(rect);
}
}
foreach (Rectangle rec in listRec)
{
g = pictureBox1.CreateGraphics();
Pen p = new Pen(Color.Blue);
g.DrawRectangle(p, rec);
}
}
public void ChangeColor(Rectangle target, Color targetColor)
{
Pen p = new Pen(targetColor);
g.DrawRectangle(p, target.X, target.Y, target.Width, target.Height);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.D0: ChangeColor(listRec[0], Color.Red);
break;
case Keys.D1: ChangeColor(listRec[1], Color.Red);
break;
//..more code to handle all keys..
}
}
}
If you're not concerned with performance or the looks, then the easiest approach would be to create a List of Lists of Panels in your Form_Load event as one of the comments mentions.
List<List<Panel>> panelGrid = new List<List<Panel>>();
for (var i = 0; i < 5; i++)
{
var panelRow = new List<Panel>();
for (var j = 0; j < 5; j++)
{
panelRow.Add(new Panel());
// add positioning logic here
}
panelGrid.Add(panelRow);
}
Then you will be able to reference each individual one at a later stage...
If you have to use Graphics class (which is the better approach), then you should setup something similar however replacing Panel with a class of your own. Then in Form_Paint event you would iterate through the list of objects and render them.
class MyPanel
{
public Size size;
public Color color;
}
...
foreach (var myPanelRow in myPanelGrid)
foreach (var myPanel in myPanelRow)
g.FillRectangle(myPanel.color, myPanel.size); // this obviously won't work as is, but you get the idea
Then when you need to change a color, you do something like:
myPanelsGrid[0][0].color = Color.Blue;
myForm.Invalidate();
The second line will result in Paint in event being called again.

Categories

Resources