Rob Rob - 28 days ago 8
C# Question

Custom Control Doesn't Redraw In Designer Or When Running When A Property Is Changed

I'm creating a custom control based on a Panel control and using it to draw a grid of rectangles inside of it. I have made the Columns and Rows as properties to allow them to be changed once the control is added to a form. However if the values are changed in the designer or in the form code the rectangles are never drawn and the form appears blank. I've tried different suggestions such as this.Invalidate() and this.Refresh() in the set portion of the property but I still can't seem to get it to paint the new rectangle grid. I'm sure that I'm just missing something somewhere. Here's the code I'm using:

public class Pixel
{
public Rectangle Bounds { get; set; }
public bool IsOn { get; set; }
public bool IsSelected { get; set; }
}




public class PixelGridControl : Panel
{
private int columns = 99;
private int rows = 63;
private Pixel[,] pixels;
private bool leftMouseIsDown = false;
private bool rightMouseIsDown = false;

public int Columns
{
get { return columns; }
set
{
if (value > 0)
{
columns = value;
CreatePixelGrid();
this.Refresh();
}
else
{
throw new ArgumentOutOfRangeException("Columns", "Must be > 0");
}
}
}
public int Rows
{
get { return rows; }
set
{
if (value > 0)
{
rows = value;
CreatePixelGrid();
this.Refresh();
}
else
{
throw new ArgumentOutOfRangeException("Rows", "Must be > 0");
}
}
}

public PixelGridControl()
{
this.DoubleBuffered = true;
this.ResizeRedraw = true;

CreatePixelGrid();
}

// 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.FillEllipse(Brushes.Gold, pixels[x, y].Bounds);
e.Graphics.DrawEllipse(Pens.Goldenrod, pixels[x, y].Bounds);
}
else
{
e.Graphics.FillRectangle(Brushes.Black, pixels[x, y].Bounds);
e.Graphics.DrawRectangle(Pens.White, pixels[x, y].Bounds);
}
}
}
base.OnPaint(e);
}
private void CreatePixelGrid()
{
// 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();
}
}
}
}

Answer

The problem is that you don't populate grid cell bounds when Rows or Columns change, thus they remain Rectangle.Empty.

To fix that, move the code from OnResize to a separate method:

private void UpdatePixelGrid()
{
    // adjust each column and row to fit entire client area:
    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;
    }
}

and call it from both OnResize and CreatePixelGrid:

protected override void OnResize(EventArgs e)
{
    UpdatePixelGrid();
    base.OnResize(e);
}

private void CreatePixelGrid()
{
    // 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();
        }
    }
    UpdatePixelGrid();
}