Search code examples
c#.netwinformspicturebox

Dynamically created PictureBox rendering problem


The end goal is a somewhat playable memory game. Currently, I'm stuck on a rendering problem. I have the following classes:

Field, which is an abstract UserControl:

public abstract class Field : UserControl
{
    protected PictureBox _pictureBox;

    public Field()
    {
        _pictureBox = new PictureBox();
        _pictureBox.Image = Properties.Resources.empty;
        _pictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
        _pictureBox.BorderStyle = BorderStyle.FixedSingle;
        this.Controls.Add(_pictureBox);
    }

    // ...
    // some abstract methods, not currently important
}

MemoryField, which derives from Field:

public class MemoryField : Field
{
    public MemoryField(Form parent, int xPos, int yPos, int xSize, int ySize)
    {
        _pictureBox.ClientSize = new Size(xSize, ySize);
        _pictureBox.Location = new Point(xPos, yPos);
        _pictureBox.Parent = parent;
    }

    // ...
}

And finally, MainForm which is an entry point for my application:

public partial class MainForm : Form
{
    private readonly int fieldWidth = 100; // 150 no rendering problems at all
    private readonly int fieldHeight = 100;

    public MainForm() { InitializeComponent(); }

    private void MainForm_Load(object sender, EventArgs e)
    {
        for (int y = 0; y < 6; y++) // 6 rows
        {
            for (int x = 0; x < 10; x++) // 10 columns
            {
                Field field = new MemoryField(this, 
                    x * (fieldWidth + 3), // xPos, 3 is for a small space between fields
                    labelTimer.Location.Y + labelTimer.Height + y * (fieldHeight + 3), // yPos
                    fieldWidth, 
                    fieldHeight);

                this.Controls.Add(field);
            }
        }
    }
}

Here's where my problem lies: In those for loops I'm trying to generate a 6x10 grid of Fields (with each containing a PictureBox 100x100 px in size). I do that almost successfully, as my second field is not rendered correctly: Rendering problem with field size of 100x100 px

Only thing I found that works (fixes the problem completely) is making field bigger (i.e. 150px). On the other hand, making it smaller (i.e. 50px) makes the problem even bigger:

Rendering problem with field size of 50x50 px


Maybe useful information and things I've tried:

  • My MainForm is AutoSize = true; with AutoSizeMode = GrowAndShrink;
  • My MainForm (initially) doesn't contain any components except menuStrip and label
  • I tried changing PictureBox.Image property, that didn't work.
  • I tried creating the grid with just PictureBox controls (not using Field as a PictureBox wrapper), that did work.
  • I tried placing labelTimer in that "problematic area" which does fix the problem depending on where exactly I put it. (because field positioning depends on labelTimer 's position and height)
  • I tried relaunching visual studio 2017, didn't work.

Of course, I could just change the size to 150px and move on, but I'm really curious to see what's the root of this problem. Thanks!


Solution

  • The easiest thing to do to fix the problem is something you've already tried - using directly a PictureBox instead of a Field. Now, considering that you only use Field to wrap a PictureBox, you could inherit from PictureBox instead of just wrapping it. Changing your classes to these will fix the issue as you've noticed:

    public abstract class Field : PictureBox {
    
        public Field() {
            Image = Image.FromFile(@"Bomb01.jpg");
            SizeMode = PictureBoxSizeMode.StretchImage;
            BorderStyle = BorderStyle.FixedSingle;
            Size = new Size(100, 100);
        }
    
        // ...
        // some abstract methods, not currently important
    }
    
    public class MemoryField : Field {
        public MemoryField(Form parent, int xPos, int yPos, int xSize, int ySize) {
            ClientSize = new Size(xSize, ySize);
            Location = new Point(xPos, yPos);
        }
    
        // ...
    }
    

    The real reason it was not working has to do with both sizing and positioning of each Field and their subcomponents. You should not set the Location of each _pictureBox relatively to its parent MemoryField, but rather change the Location of the MemoryField relatively to its parent Form.

    You should also set the size of your MemoryField to the size of its child _pictureBox otherwise it won't size correctly to fit its content.

    public class MemoryField : Field {
        public MemoryField(Form parent, int xSize, int ySize) {
            _pictureBox.ClientSize = new Size(xSize, ySize);
            // I removed the setting of Location for the _pictureBox.
            this.Size = _pictureBox.ClientSize; // size container to its wrapped PictureBox
            this.Parent = parent; // not needed
        }
        // ...
    }
    

    and change your creation inner loop to

    for (int x = 0; x < 10; x++) // 10 columns
    {
        Field field = new MemoryField(this,
            fieldWidth,
            fieldHeight);
        field.Location = new Point(x * (fieldWidth + 3), 0 + 0 + y * (fieldHeight + 3)); // Set the Location here instead!
        this.Controls.Add(field);
    }