I'm trying to find the best path to go about creating a pixel "grid" that would allow basic paint functions such as coloring the pixels by clicking and moving the mouse, selecting an area for copying, pasting, or moving, or using other graphics functions in order to render text or shapes to the pixels. I've looked at a few samples, such as this example which overrides the panel control and has a similar look to what I'm trying to achieve, but the painting is slow and it seems like it wouldn't perform well for drawing with the mouse. Is there a control, or one that I could override, that would allow for the functionality that I'm looking for?
Here's a sample of what the above example looks like:
Sample pixel grid
And the code I adapted from the above example:
public class Pixel
{
public Rectangle Bounds { get; set; }
public bool IsOn { get; set; }
public bool IsSelected { get; set; }
}
public class PixelGridControl : Panel
{
public int Columns = 99;
public int Rows = 63;
private readonly Pixel[,] pixels;
public PixelGridControl()
{
this.DoubleBuffered = true;
this.ResizeRedraw = true;
// initialize pixel grid:
pixels = new Pixel[Columns, Rows];
for (int y = 0; y < Rows; ++y)
{
for (int x = 0; x < Columns; ++x)
{
pixels[x, y] = new Pixel();
}
}
}
// adjust each column and row to fit entire client area:
protected override void OnResize(EventArgs e)
{
int top = 0;
for (int y = 0; y < Rows; ++y)
{
int left = 0;
int height = (this.ClientSize.Height - top) / (Rows - y);
for (int x = 0; x < Columns; ++x)
{
int width = (this.ClientSize.Width - left) / (Columns - x);
pixels[x, y].Bounds = new Rectangle(left, top, width, height);
left += width;
}
top += height;
}
base.OnResize(e);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
for (int y = 0; y < Rows; ++y)
{
for (int x = 0; x < Columns; ++x)
{
if (pixels[x, y].IsOn)
{
e.Graphics.FillRectangle(Brushes.Gold, pixels[x, y].Bounds);
}
else
{
ControlPaint.DrawButton(e.Graphics, pixels[x, y].Bounds,
ButtonState.Normal);
}
}
}
base.OnPaint(e);
}
// determine which button the user pressed:
protected override void OnMouseDown(MouseEventArgs e)
{
for (int y = 0; y < Rows; ++y)
{
for (int x = 0; x < Columns; ++x)
{
if (pixels[x, y].Bounds.Contains(e.Location))
{
pixels[x, y].IsOn = true;
this.Invalidate();
MessageBox.Show(
string.Format("You pressed on button ({0}, {1})",
x.ToString(), y.ToString())
);
}
}
}
base.OnMouseDown(e);
}
}
Here is an minimal example that uses a bitmap as the pixel storage and displays the enlarged pixels in a panel for editing.
You can use it by setting it up to edit the pixels in an Picturebox pBox_Target.Image, maybe like this:
public Form1()
{
InitializeComponent();
..
..
pixelEditor1.APBox = pBox_Target;
pixelEditor1.TgtBitmap = (Bitmap)pixelEditor1.APBox.Image;
..
}
The editor exposes the pixel size and the color to draw with among a minimum of properties..
all in all it is slightly over 100 lines - of course it is meant to be expanded!
Here it is at work:
class PixelEditor : Panel
{
public Color DrawColor { get; set; }
public Color GridColor { get; set; }
int pixelSize = 8;
public int PixelSize
{
get { return pixelSize; }
set
{
pixelSize = value;
Invalidate();
}
}
public Bitmap TgtBitmap { get; set; }
public Point TgtMousePos { get; set; }
Point lastPoint = Point.Empty;
PictureBox aPBox = null;
public PictureBox APBox {
get { return aPBox; }
set
{
if (value == null) return;
aPBox = value;
aPBox.MouseClick -=APBox_MouseClick;
aPBox.MouseClick +=APBox_MouseClick;
}
}
private void APBox_MouseClick(object sender, MouseEventArgs e)
{
TgtMousePos = e.Location;
Invalidate();
}
public PixelEditor()
{
DoubleBuffered = true;
BackColor = Color.White;
GridColor = Color.DimGray;
DrawColor = Color.Red;
PixelSize = 10;
TgtMousePos = Point.Empty;
if (APBox != null && APBox.Image != null)
TgtBitmap = (Bitmap)APBox.Image;
MouseClick +=PixelEditor_MouseClick;
MouseMove +=PixelEditor_MouseMove;
Paint +=PixelEditor_Paint;
}
private void PixelEditor_Paint(object sender, PaintEventArgs e)
{
if (DesignMode) return;
Graphics g = e.Graphics;
int cols = ClientSize.Width / PixelSize;
int rows = ClientSize.Height / PixelSize;
if (TgtMousePos.X < 0 || TgtMousePos.Y < 0) return;
for (int x = 0; x < cols; x++)
for (int y = 0; y < rows; y++)
{
int sx = TgtMousePos.X + x;
int sy = TgtMousePos.Y + y;
if (sx > TgtBitmap.Width || sy > TgtBitmap.Height) continue;
Color col = TgtBitmap.GetPixel(sx, sy);
using (SolidBrush b = new SolidBrush(col))
using (Pen p = new Pen(GridColor))
{
Rectangle rect = new Rectangle(x * PixelSize, y * PixelSize,
PixelSize, PixelSize);
g.FillRectangle(b, rect);
g.DrawRectangle(p, rect);
}
}
}
private void PixelEditor_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return;
int x = TgtMousePos.X + e.X / PixelSize;
int y = TgtMousePos.Y + e.Y / PixelSize;
if (new Point(x, y) == lastPoint) return;
Bitmap bmp = (Bitmap)APBox.Image;
bmp.SetPixel(x,y, DrawColor);
APBox.Image = bmp;
Invalidate();
lastPoint = new Point(x, y);
}
private void PixelEditor_MouseClick(object sender, MouseEventArgs e)
{
int x = TgtMousePos.X + e.X / PixelSize;
int y = TgtMousePos.Y + e.Y / PixelSize;
Bitmap bmp = (Bitmap)APBox.Image;
bmp.SetPixel(x,y, DrawColor);
APBox.Image = bmp;
Invalidate();
}
}
Note that this solution neither uses a special pixel class nor makes any provisions for selecting sets of pixels. To implement most selection tools you can use a GraphicsPath; you can the e.g. fill the path or loop over its bounds using IsVisible to enumerate the pixels in the path..
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
Related
using System.Drawing;
using System.Windows.Forms;
public partial class Form1 : Form
{
int vx = 5;
int vy = 5;
int bx = 0;
int by = 50;
int px = 93;
public Form1()
{
InitializeComponent();
timer1.Interval = 100;
timer1.Start();
}
public class Ball
{
public int X;
public int Y;
public int W;
public int H;
public Ball(int x, int y, int w, int h)
{
X = x;
Y = y;
W = w;
H = h;
}
}
public class Paddle
{
public int X;
public int Y;
public int W;
public int H;
public Paddle(int x, int y, int w, int h)
{
X = x;
Y = y;
W = w;
H = h;
}
}
public class Brick
{
public int X;
public int Y;
public int W;
public int H;
public Brick(int x, int y, int w, int h)
{
X = x;
Y = y;
W = w;
H = h;
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
int[] brickxs = { 0, 51, 102, 153, 204, 255, 306, 357, 408, 459, 510, 561, 612, 663, 714, 765 };
int bc = 0;
SolidBrush blueBrush = new SolidBrush(Color.Blue);
Ball b = new Ball(55, 55, 25, 25);
Paddle p = new Paddle(93, 377, 130, 30);
Brick br = new Brick(20, 20, 51, 20);
br.X = 0;
while (bc < 16)
{
br.X = brickxs[bc];
System.Drawing.SolidBrush myBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Red);
System.Drawing.Graphics formGraphics;
formGraphics = this.CreateGraphics();
formGraphics.FillRectangle(myBrush, new Rectangle(br.X, 0, 49, 20));
myBrush.Dispose();
formGraphics.Dispose();
bc = bc + 1;
}
Rectangle ball = new Rectangle(bx, by, b.W, b.H);
Rectangle paddle = new Rectangle(px, p.Y, p.W, p.H);
//Rectangle brick = new Rectangle(br.X, br.Y, br.W, br.H);
e.Graphics.FillEllipse(blueBrush, ball);
e.Graphics.FillRectangle(blueBrush, paddle);
//e.Graphics.FillRectangle(blueBrush, brick);
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == Keys.Right)
{
px += 5;
}
}
private void MoveTimer_Tick(object sender, EventArgs e)
{
Invalidate();
}
private void timer1_Tick(object sender, EventArgs e)
{
bx = bx + vx;
by = by + vy;
if (px <= 0)
{
px = 0;
}
if (px >= 771)
{
px = 771;
}
WallCollision();
floorandCeilingCollision();
Invalidate();
}
public void WallCollision()
{
if (bx >= 771)
{
vx = -5;
}
if (bx <= 0)
{
vx += 5;
}
}
public void floorandCeilingCollision()
{
if (by >= 420)
{
vy = -5;
}
if (by <= 0)
{
vy = 5;
}
}
}
I am creating a game and I need some help.
In my code have classes for each of the parts of the game: the ball, paddle and bricks. The array positions the bricks.
I want to move the paddle (which just a rectangle) left and right with the arrow keys. I tried to use the key down method but it did not work.
Could you suggest any solutions or point out anything that I left out?
Personally, I use e.KeyCode instead of e.KeyData, try this first.
Make sure your Form is focused, and not a picturebox or something else you might have in the game. Because you try to call the KeyDown event for your Form, not for a control inside your Form.
I never used a Paint event, are you sure it is called? It might be the case that your game registeres the movement but never shows the changes to you. I usually have a separate method for drawing and I call it every time there is a change, you should try this too.
If nothing works, try debugging. Set a break point in your KeyDown method to see if it is called. If it does, set it in the Paint method. This one will surely be called once, at runtime, but if you click "Continue" on that time and try to move your object. If it is not called any other time, then here is your answer :)
Please update me with what you find after trying this things, and ask me what to do next if you get stuck or simply don't know what else there is to do :)
Trying to display a graph on a Form application.
Created a PictureBox control and initialized this class with its value.
On resize, graph always updates; on mouse scroll, it hardly does.
It's GraphBox , PictureBox control, inside a GraphBoxPanel, Panel control.
This the class:
public struct DLT_measure_item
{
public DateTime ts;
public float value;
public int id;
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct dlt_ser_meas
{
public byte msg_id; // 'D'
public byte meas_count; // Number of measures
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public byte[] port; // Module ID (4b) + Port ID (4b)
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public float[] meas; // measure
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public byte[] msg_end;
}
public class manageGraph
{
private PictureBox box;
public bool displayGrid = true;
private int horAxisMin_p = 0;
private int horAxisMax_p = 300;
private float verAxisMin_p = 0;
private float verAxisMax_p = 40;
public int horAxisMin
{
get { return this.horAxisMin_p; }
set
{
if (value < horAxisMax_p)
{
this.horAxisMin_p = value;
reDraw();
}
}
}
public int horAxisMax
{
get { return this.horAxisMax_p; }
set
{
if (value > horAxisMin_p)
{
this.horAxisMax_p = value;
reDraw();
}
}
}
public float verAxisMin
{
get { return this.verAxisMin_p; }
set
{
if (value < verAxisMax_p)
{
this.verAxisMin_p = value;
verPointPerUnit = graphArea.Height / (verAxisMax_p - this.verAxisMin_p);
}
}
}
public float verAxisMax
{
get { return this.verAxisMax_p; }
set
{
if (value > verAxisMin_p)
{
this.verAxisMax_p = value;
verPointPerUnit = graphArea.Height / (this.verAxisMax_p - verAxisMin_p);
}
}
}
Pen axes = new Pen(Color.Black, (float)1.5);
public int horAxisSpacing = 30;
public int verAxisSpacing = 20;
public int horAxis = 20;
public int verAxis = 20;
private float horPointPerUnit = 1;
private float verPointPerUnit = 1;
public int horAxisTickLen = 5;
public int verAxisTickLen = 5;
public bool horAxisShowTime = false;
private Rectangle graphArea = new Rectangle();
public void reDraw()
{
box.Image.Dispose();
Bitmap GraphBlankImage = new Bitmap(box.Width, box.Height);
box.Image = GraphBlankImage;
updatePointPerUnit();
drawGrid();
box.Refresh();
}
public manageGraph(PictureBox targetImageBoxbox)
{
box = targetImageBoxbox;
horAxisMin_p = 0;
horAxisMax_p = 300;
verAxisMin_p = 0F;
verAxisMax_p = 50F;
updatePointPerUnit();
}
private Point measToPoint(DLT_measure_item measure) {
Point coords = new Point();
coords.X = graphArea.Width - (int)(
((DateTime.Now - measure.ts).TotalSeconds + horAxisMin_p) * horPointPerUnit ) ;
coords.Y = graphArea.Height - (int)(
((measure.value - verAxisMin_p) * verPointPerUnit));
return coords;
}
public manageGraph(PictureBox targetImageBoxbox,
int xmin, int xmax, float ymin, float ymax)
{
box = targetImageBoxbox;
horAxisMin_p = xmin;
horAxisMax_p = xmax;
verAxisMin_p = ymin;
verAxisMax_p = ymax;
updatePointPerUnit();
}
private void updateGraphArea()
{
graphArea = new Rectangle(0, 0, box.Width - horAxis, box.Height - verAxis);
}
private void updatePointPerUnit()
{
updateGraphArea();
horPointPerUnit = graphArea.Width / (horAxisMax_p - horAxisMin_p);
verPointPerUnit = graphArea.Height / (verAxisMax_p - verAxisMin_p);
}
public void drawGrid()
{
//updatePointPerUnit();
using (Graphics g = Graphics.FromImage(box.Image))
{
// X axis
g.DrawLine(axes, graphArea.Left, graphArea.Bottom, box.Width, graphArea.Bottom);
// Y axis
g.DrawLine(axes, graphArea.Right + 1, graphArea.Top, graphArea.Right +1, graphArea.Bottom);
using (Font ArialFont = new Font("Arial", 10))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
// Put x labels
for (int i = 1; i <= (graphArea.Width / horPointPerUnit); i = i + (int)(horAxisSpacing / horPointPerUnit ) + 1)
{
g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Width - ( i * horPointPerUnit) - 5, graphArea.Bottom +5);
g.DrawLine(axes, graphArea.Width - (i * horPointPerUnit), graphArea.Bottom + (horAxisTickLen / 2), graphArea.Width - (i * horPointPerUnit), graphArea.Bottom - (horAxisTickLen / 2));
}
// Put y labels
for (int i = 1; i <= (graphArea.Height / verPointPerUnit); i = i + (int)(verAxisSpacing / verPointPerUnit) +1)
{
g.DrawString((i).ToString(), ArialFont, Brushes.Black, graphArea.Right + 1 , graphArea.Height - (i * verPointPerUnit) - 8);
g.DrawLine(axes, graphArea.Width - (verAxisTickLen / 2), (i * verPointPerUnit), graphArea.Width + (verAxisTickLen / 2), (i * verPointPerUnit));
}
}
/*Put some random data*/
DLT_measure_item testmeas = new DLT_measure_item();
Point testGraphPoint = new Point();
testmeas.ts = DateTime.Now;
testmeas.value = 0;
testGraphPoint = measToPoint(testmeas);
g.FillEllipse(Brushes.Blue, testGraphPoint.X, testGraphPoint.Y, 4, 4);
for (double i = 0; i < 300; i++)
{
double x = i;
double freq = 10;
double y = 30 - (i/10);
testmeas.value = (float)y;
testmeas.ts = DateTime.Now.AddSeconds(-1 * i);
testGraphPoint = measToPoint(testmeas);
g.FillEllipse(Brushes.Red, testGraphPoint.X, testGraphPoint.Y, 2,2);
}
}
}
}
The initialization:
public DLThermalogMainForm()
{
InitializeComponent();
Bitmap GraphBlankImage = new Bitmap(GraphBox.Width, GraphBox.Height);
GraphBox.Image = GraphBlankImage;
myGraph = new manageGraph(GraphBox);
myGraph.drawGrid();
}
Those the handlers:
private void Form1_ResizeEnd(object sender, EventArgs e)
{
myGraph.reDraw();
OutputTextBox.AppendText("Resize." + Environment.NewLine);
}
private void GraphBox_MouseMove(object sender, MouseEventArgs e)
{
//Get the focus to have the wheel working
GraphBoxPanel.Focus();
}
private void GraphBoxPanel_MouseWheel(object sender, MouseEventArgs e)
{
// Change x axis max value to zoom in/out the graph
myGraph.horAxisMax += e.Delta/ 120;
myGraph.reDraw();
}
On resize event, it always redraws; on mouse wheel, it does quickly only with small horAxisMax values (does it makes sense???), but for larger, it takes many seconds to update, or doesn't at all.
Thank you very much
Change reDraw like this:
public void reDraw()
{
box.Image.Dispose();
Bitmap GraphBlankImage = new Bitmap(box.ClientSize.Width, box.ClientSize.Height);
updatePointPerUnit();
drawGrid(GraphBlankImage);
box.Image = GraphBlankImage;
}
and drawGrid like this:
public void drawGrid(Bitmap bmp)
{
//updatePointPerUnit(); //??
using (Graphics g = Graphics.FromImage(bmp))
{
...
...
...
}
}
Now the Bitmap with the grid should immediately show up in the PictureBox.
As mentioned a Graphics object is a tool to change an associated Bitmap. To pick it up the Bitmap should be assigned to the PictureBoxe's Image.
Also note, that unless your PictureBox has no Border, there is a small difference between the outer size (aka Bounds) and the inner size, the ClientRectangle / ClientSize. The Image should have the ClientSize
You may wonder why your original code doesn't work? After all an Image is a reference type, so changing it, as you did should be enough..
But looking deeper into the source code we find the reason:
The PictureBox's Image is a property and in its setter there is a call to InstallNewImage:
public Image Image {
get {
return image;
}
set {
InstallNewImage(value, ImageInstallationType.DirectlySpecified);
}
}
The same call is also in a few other places like Load or in the setter of ImageLocation. But changing the Image behind the scene alone will not force the PictureBox to make that call. A Refresh() should also do it.. And, as you found out, resizing it will also cause the PictureBox to pick up the changed data in the Image Bitmap..
The easiest way to force updating is simply to invalidate the control.
timer = new Timer();
timer.Interval = 200; //refreshes every 200 ms
timer.Tick += (sender,e) => targetImageBoxbox.Invalidate();
timer.Start();
I'm currently trying to make a mask generator to generate polygons for images. Here's my code.
Bitmap bmp = new Bitmap(file);
int w = bmp.Width;
int h = bmp.Height;
List<Point> vertices = new List<Point>();
for (int y=0; y<h; y++)
{
bool rowbegin = false;
for (int x=0; x<w; x++)
{
Color c = bmp.GetPixel(x, y);
if (!rowbegin)
{
// Check for a non alpha color
if (c.A != Color.Transparent.A)
{
rowbegin = true;
// This is the first point in the row
vertices.Add(new Point(x, y));
}
} else {
// Check for an alpha color
if (c.A == Color.Transparent.A)
{
// The previous pixel is the last point in the row
vertices.Add(new Point(x-1, y));
rowbegin = false;
}
}
}
}
// Convert to an array of points
Point[] polygon = vertices.ToArray();
Graphics g = Graphics.FromImage(bmp);
g.DrawPolygon(Pens.LawnGreen, polygon);
g.Dispose();
But I'm getting wrong output.
The required.
What I want is also the gaps between the characters to be empty. Also why DrawPolygon is filling it?
Thanks.
Do you trying something like this:i hope my code help you:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Bitmap bmp = new Bitmap(#"C:\Users\Ali\Desktop\1.png");
int w = bmp.Width;
int h = bmp.Height;
Lst_Data lastpointcolor = new Lst_Data() ;
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
Color c = bmp.GetPixel(x, y);
if (c.A != Color.Transparent.A)
{
if (lastpointcolor.color.A == Color.Transparent.A)
{
bmp.SetPixel(lastpointcolor.point.X, lastpointcolor.point.Y, Color.Red);
}
}
lastpointcolor = new Lst_Data() { point = new Point(x, y), color = bmp.GetPixel(x, y) };
}
}
for (int y = h-1; y > 0; y--)
{
for (int x = w-1; x > 0; x--)
{
Color c = bmp.GetPixel(x, y);
if (c.A != Color.Transparent.A)
{
if (lastpointcolor.color.A == Color.Transparent.A)
{
bmp.SetPixel(lastpointcolor.point.X, lastpointcolor.point.Y, Color.Red);
}
}
lastpointcolor = new Lst_Data() { point = new Point(x, y), color = bmp.GetPixel(x, y) };
}
}
pictureBox1.Image = bmp;
}
}
public struct Lst_Data
{
public Point point;
public Color color;
}
}
Edited:
And Another idea i have for you:D
Make a mask for original image with the same size and do this:
Bitmap orginal = new Bitmap(#"C:\Users\Ali\Desktop\orginal.png");
Bitmap mask = new Bitmap(#"C:\Users\Ali\Desktop\mask.png");
for (int i = 0; i < orginal.Width; i++)
{
for (int j = 0; j < orginal.Height; j++)
{
if (orginal.GetPixel(i, j).A == Color.Transparent.A)
{
mask.SetPixel(i, j, Color.Transparent);
}
}
}
Bitmap bitmap = new Bitmap(mask, mask.Height, mask.Height);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(mask, 0, 0);
g.DrawImage(orginal,0,0);
}
pictureBox1.Image = bitmap;
Result:
I have created a UserControl that split the screen into rectangles but my control is executing the OnPaintBackground Many times whilst it should execut it just one please help me because it's really important to execut it just once because the scrren starts flickaring much.
public partial class Schedual : UserControl
{
int days;
public int Days
{
get { return days; }
set
{
days = value;
change = true;
Invalidate(true);
}
}
int periods;
public int Periods
{
get { return periods; }
set
{
periods = value;
change = true;
Invalidate(true);
}
}
Brush brush;
bool change = false;
List<Panel> panels;
public Schedual()
{
InitializeComponent();
this.ResumeLayout(true);
this.days = 1;
this.periods = 1;
brush = Brushes.White;
change = false;
}
protected override void OnPaint(PaintEventArgs e)
{
//stuff ....... or base.OnPaint(e);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
Graphics g = e.Graphics;
var h = this.Height / days;
var w = this.Width / periods;
for (int i = 0; i < days; i++)
{
for (int j = 0; j <= periods; j++)
{
g.FillRectangle(brush, j * w, i * h, w, h);
if (change)
{
AddPanel(j * w, i * h, w, h);
}
g.DrawLine(Pens.Black, 0, i * h, this.Right, i * h); //draw the horizantle lines
g.DrawLine(Pens.Black, j * w, 0, this.Bottom, j * w); //draw the verical lines
}
}
change = false;
}
}
the output is that the screen is flickaring much when drawing the background over and over again ......
Double buffering...
See more info here - http://msdn.microsoft.com/en-us/library/3t7htc9c.aspx
I cannot for the life of me work out how to get the block that is supposed to be drawn with every loop through the array of "Arxl" objects to animate across the grid.
Any suggestions would be really appreciated, not looking for someone to complete the code for me. just a fresh set of eyes.
public partial class Game : Form
{
//attributes
private Bitmap _grid;
private Arxl[,] _cartesianGrid;
private int _arxlAmount;
const int ARXL = 4;
public Game()
{
InitializeComponent();
_arxlAmount = (gridPictureBox.Height / ARXL);//in case height/arxl is not an even number?
_cartesianGrid = new Arxl[_arxlAmount, _arxlAmount];
_grid = new Bitmap(gridPictureBox.Width, gridPictureBox.Height);
int x;
int y;
for (x = 0; x < _arxlAmount; x++)
{
for (y = 0; y < _arxlAmount; y++)
{
_cartesianGrid[x, y] = new Arxl();
}
}
SetSeed(_cartesianGrid);
}
private void SetSeed(Arxl[,] cartesianGrid)
{
_cartesianGrid[1, 1].Active = true;
}
private void DrawArxl(Bitmap _grid, Arxl[,] cartesianGrid,int arxlAmount)
{
int x, y;
x=0;
y=0;
Graphics graphics = Graphics.FromImage(_grid);
graphics.Clear(Color.White);
for (x = 1; x < arxlAmount;x++ )
{
for (y = 1; y < arxlAmount; y++)
{
if (cartesianGrid[x, y].Active==true)
{
cartesianGrid[x, y].Area = new Rectangle(x * ARXL, y * ARXL, ARXL, ARXL);
graphics.FillRectangle(Brushes.Black, cartesianGrid[x, y].Area);
}
else if(cartesianGrid[x,y].Active==false)
{
Pen newPen=new Pen(Color.Black);
cartesianGrid[x, y].Area = new Rectangle(x * ARXL, y * ARXL, ARXL, ARXL);
graphics.DrawRectangle(newPen,cartesianGrid[x, y].Area);
newPen.Dispose();
}
}
}
}
private void timer_Tick(object sender, EventArgs e)
{
//GameOfLife(_cartesianGrid, _arxlAmount);
ScrollBlock(_cartesianGrid, _arxlAmount);
DrawArxl(_grid, _cartesianGrid, _arxlAmount);
gridPictureBox.Image = _grid;
}
private void ScrollBlock(Arxl[,] cartesianGrid, int arxlAmount)
{
int x = 0;
int y = 0;
for (x = 0; x < arxlAmount; x++)
{
for (y = 0; y < arxlAmount; y++)
{
if (_cartesianGrid[x, y].Active == true)
{
if (x>=0)
{
if (x == (arxlAmount-1))
{
_cartesianGrid[x, y].Active = false;
_cartesianGrid[1, y].Active = true;
}
else if(x<(arxlAmount-1))
{
_cartesianGrid[x, y].Active = false;
_cartesianGrid[x+1, y].Active = true;
}
}
}
}
}
}
According to a comment in your code, you want to program the life game. It will not work, if you change the cells in place, because you will have to compute the new state from the unchanged old state. Therefore, you will need to have two game boards, one with the current state and one with the new state. Instead of creating new board all the time, it is better to have two boards and to swap them. In addition, there is no point in storing the Rectangles in the board. Therefore, I declare the boards as Boolean matrix.
const int CellSize = 4;
private int _boardSize;
private bool[,] _activeBoard, _inactiveBoard;
Bitmap _grid;
The form constructor is changed like this
public Game()
{
InitializeComponent();
_boardSize = Math.Min(gridPictureBox.Width, gridPictureBox.Height) / CellSize;
_grid = new Bitmap(gridPictureBox.Width, gridPictureBox.Height);
_activeBoard = new bool[_boardSize, _boardSize];
_inactiveBoard = new bool[_boardSize, _boardSize];
SetSeed();
}
We initialize the game like this (as an example)
private void SetSeed()
{
_activeBoard[0, 0] = true;
_activeBoard[7, 4] = true;
DrawGrid();
}
The timer tick does this
ScrollBlock();
DrawGrid();
The logic in ScrollBlock is completely new. We look at the state on the _activeBoard and set the state of _inactiveBoard. Then we swap the two boards.
private void ScrollBlock()
{
for (int x = 0; x < _boardSize; x++) {
for (int y = 0; y < _boardSize; y++) {
if (_activeBoard[x, y]) {
_activeBoard[x, y] = false;
int newX = x + 1;
int newY = y;
if (newX == _boardSize) {
newX = 0;
newY = (newY + 1) % _boardSize;
}
_inactiveBoard[newX, newY] = true;
}
}
}
SwapBoards();
}
The boards are simply swapped like this
private void SwapBoards()
{
bool[,] tmp = _activeBoard;
_activeBoard = _inactiveBoard;
_inactiveBoard = tmp;
}
And finally DrawGrid draws the _activeBoard
private void DrawGrid()
{
Graphics graphics = Graphics.FromImage(_grid);
graphics.Clear(Color.White);
for (int x = 0; x < _boardSize; x++) {
for (int y = 0; y < _boardSize; y++) {
var rect = new Rectangle(x * CellSize, y * CellSize, CellSize, CellSize);
if (_activeBoard[x, y]) {
graphics.FillRectangle(Brushes.Black, rect);
} else {
graphics.DrawRectangle(Pens.Black, rect);
}
}
}
gridPictureBox.Image = _grid;
}
I've spotted your problem.
The problem is that you're updating a cell position (moving it to the right in this particular initial state), but the next iteration in the for loop finds the updated state from the previous iteration, so it updates the cell again, and again, and when the cycle stops, the cell was scrolled over to its initial cell position!, with no repainting in between.
I'm modifying your code to add an UpdateList that will turn on cells that need to be ON after the grid scan has finished to avoid updating the same "active dot" more than once. This should show a moving dot from left to right.
private void ScrollBlock(Arxl[,] cartesianGrid, int arxlAmount) {
int x = 0;
int y = 0;
List<Point> updateList = new List<Point>();
for( x = 0; x < arxlAmount; x++ ) {
for( y = 0; y < arxlAmount; y++ ) {
if( _cartesianGrid[x, y].Active == true ) {
if( x >= 0 ) {
if( x == (arxlAmount - 1) ) {
_cartesianGrid[x, y].Active = false;
//_cartesianGrid[1, y].Active = true;
updateList.Add(new Point(1, y));
} else if( x < (arxlAmount - 1) ) {
_cartesianGrid[x, y].Active = false;
//_cartesianGrid[x + 1, y].Active = true;
updateList.Add(new Point(x + 1, y));
}
}
}
}
}
foreach( var pt in updateList ) {
_cartesianGrid[pt.X, pt.Y].Active = true;
}
}
In your timer try calling gridPictureBox.Invalidate() after you assign the image to the picturebox. This will force the picturebox to redraw itself.