4

So basically I've got a game board going, represented by a TableLayoutPanel. Each cell of the TableLayoutPanel represents a space on the board and contains a Panel. Each Panel has a Label in it to display what is currently in that space (e.g. a character or building) and its BackColor represents what kind of terrain that space is (e.g. Land, Water, etc.). When the player attempts to move a character, each possible space that that character can move will become "highlighted." The player will then double click one of the highlighted spaces to move the character there.

In order to highlight a space, I add a Panel to the existing Panel (the one that already has a Label in it).

So to recap, TableLayoutPanel --> Panel --> Label, Panel.

The main problem I'm having is that I can't get the "highlight-Panel" to center within its parent Panel; it's always Top-Left Centered. I want to be able to see part of the BackColor of the parent Panel in order to make it easier for the player to decide where to go. Thus, setting Dock to Fill is not an option. The highlight-Panel is slightly smaller than the parent Panel, thereby allowing for the edges of the parent Panel's BackColor to be visible. Yes, Anchor is set to None.

The secondary problem is that I cannot get any of the Labels' text colors to change. I've tried changing it at initialization or more directly with a specific location using "tableLayoutPanel.GetControlFromPosition(16, 4).Controls[0].ForeColor = Color.White;" but nothing seems to work.

To be honest, I'm not interested in super complicated hack fixes. But if there's something simple or something I missed, I'd really appreciate some input. Thanks.

This is the code that creates the TableLayoutPanel, the Panels within that, and the Labels within each Panel:

// Create TableLayoutPanel to hold Panels
tableLayoutPanel = new TableLayoutPanel()
{
    RowCount = 1,
    ColumnCount = 1,
    AutoSize = true,
    AutoSizeMode = AutoSizeMode.GrowAndShrink,
    Location = new Point(12, 12),
    Dock = DockStyle.Fill,
    AutoScroll = true,  
};

// Add tableLayoutPanel to the form
this.Controls.Add(tableLayoutPanel);

// Reset and add rows/columns + styles
tableLayoutPanel.RowStyles.Clear();
tableLayoutPanel.ColumnStyles.Clear();

for (int i = 0; i < rows; i++)
{
    tableLayoutPanel.RowCount++;
    tableLayoutPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 50F));
}
tableLayoutPanel.RowCount--;

for (int j = 0; j < cols; j++)
{
    tableLayoutPanel.ColumnCount++;
    tableLayoutPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
}
tableLayoutPanel.ColumnCount--;

// Add Panels to TableLayoutPanel
for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < cols; j++)
    {
        // Create new Panel
        Panel space = new Panel()
        {
            BackColor = SystemColors.ActiveCaption,
            BorderStyle = BorderStyle.FixedSingle,
            Margin = new Padding(0),
            Size = new Size(45, 45)
        };

        space.MouseClick += new MouseEventHandler(clickOnSpace);

        // Create new Label
        Label info = new Label()
        {
            Size = new Size(93, 93),
            Text = "Info",
            Dock = DockStyle.Fill,
            TextAlign = ContentAlignment.MiddleCenter,
            Enabled = false,
            Font = new Font("Microsoft Sans Serif", 6),
            ForeColor = Color.White
        };

        // Add Label to Panel
        space.Controls.Add(info);

        // Add Panel to TableLayoutPanel
        tableLayoutPanel.Controls.Add(space, j, i);
    }
}

And the code that creates the highlight-Panel:

// Highlight potential positions using possibleMoves
private void Highlight(List<Tuple<int, int>> possibleMoves, int remaining)
{
    int r = 0;
    int c = 0;

    foreach (Tuple<int, int> pair in possibleMoves)
    {
        r = pair.Item1;
        c = pair.Item2;

        // If highlight-Panel doesn't already exist
        if (tableLayoutPanel.GetControlFromPosition(c, r).Controls.Count == 1)
        {
            // Create a Panel to highlight the space
            Panel highlight = new Panel()
            {
                Name = "highlight",
                Size = new Size(30, 30),
                BackColor = Color.Yellow,
                Anchor = AnchorStyles.None
            };

            highlight.MouseDoubleClick += new MouseEventHandler(doubleClickOnSpace);

            // Add highlight Panel to space Panel
            tableLayoutPanel.GetControlFromPosition(c, r).Controls.Add(highlight);
           // Bring highlight Panel to front
            tableLayoutPanel.GetControlFromPosition(c, r).Controls[1].BringToFront();
        }
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Nick08
  • 157
  • 1
  • 2
  • 10
  • I think that you could simplify your problem by creating a user control that has all the required controls in it. The highlight panel would also be there already but hidden (Opacity = 0) so you could easily show it and hide it whenever you want to. Even better would be to create a custom control where you paint the background using GDI+. That way you don't have to deal with lots of controls and can customize the look and feel very well. Windows Forms is notoriously bad in terms of performance when you have too many controls on one form. – Timo Salomäki Aug 16 '16 at 14:48
  • You can always specify geometry management in terms of precise coordinates. If panel within panel isn't doing what you want, consider using a panel within an enclosing frame. – Bruce David Wilner Aug 16 '16 at 15:49
  • Great advice, hankide. The guy is having trouble using well-documented geometry management features of a packaged widget (I insist on X11 terminology, "control" being awfully generic), so recommend that he build his own widget from scratch. – Bruce David Wilner Aug 16 '16 at 15:57
  • I don't have a whole lot of experience using WPF so I was just trying to use what's already there instead of trying to make something from scratch. But I'll look into all that. Thanks for the advice! – Nick08 Aug 16 '16 at 18:50
  • Take a look at the answer which is based on `Dock` despite your expectation, but it works properly. Also read the last *Note* paragraph :) – Reza Aghaei Aug 19 '16 at 21:07
  • Winforms programmers have a knack for looking too much into the toolbox or the Properties window. Never forget the 3rd option, keeping that panel centered just takes one line of code in an event handler for the outer panel's Resize event. – Hans Passant Aug 24 '16 at 10:30
  • Since the user said *... Thus, setting Dock to Fill is not an option.* The answer tries to show how easy `Dock` and `Padding` properties can be used to create such UI. Also about `TableLayoutPanel`, it's an alternative. In fact it's a useful alternative which let the user center a control in `TableLayoutPanel` without writing code. Getting most out of designer is not bad. Yes, handling `Resize` event is also another alternative but when there's a simple designer based solution for such task, the designer based solution is preferred. – Reza Aghaei Aug 25 '16 at 00:59

2 Answers2

2

... Thus, setting Dock to Fill is not an option.

In fact I believe setting Dock to Fill is really a good option!

Take a look at this image. It's a TableLayoutPanel having 2 columns and 2 rows. And each cell contains a Pannel which contains a Panel which contains a Label. Despite what you expect Dock property of all controls has been set to Fill!

Center a Panel in a Panel in TableLayoutPanel

I tell you settings for the first cell which you can see Red, Black and White colors in it.

The cell contains:

  • Red area, a Panel having:

    BackColor property set to Red
    Margin property set to all 0
    Padding property set to all 5
    Dock property set to Fill

  • Black area, a Panel having:

    BackColor property set to Black
    Margin property set to all 0
    Padding property set to all 5
    Dock property set to Fill

  • White label, a Label having:

    BackColor property set to White
    Margin property set to all 0
    Padding property set to all 0
    Dock property set to Fill

The structure of other cells are exactly the same, but the BackColor of Label and its parent (like the black one), to Transparent, so you see the BackColor of the most inner Panel (like the red one).

Note

The answer is completely based on setting Dock property to Fill and setting suitable Padding value for docked controls. But there is another elegant solution for keeping a control at center of a container automatically which is based on using a TableLayoutPanel having 1 row and 1 column instead of panel, and then setting Anchor property of child to None. This way the child will be centered horizontally and vertically in the TableLayoutPanel. You will find this post about it useful:

Community
  • 1
  • 1
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Wow, so I guess the padding is what makes it feasible? I actually opted for a less complicated option (as far as the amount of controls needed), which I'll share in a post – Nick08 Aug 31 '16 at 12:42
  • Yes, it's the job of `Padding`. Such answer is useful when you want to rely on controls and current features of controls. Also always using custom paint controls is an option which is useful for advanced users. – Reza Aghaei Aug 31 '16 at 13:09
0

So I actually reached a less complicated method. Instead of having a Panel within a Panel, I opted for keeping my original Panel, and creating a custom Label, which has an outside border whose thickness and color can be changed. This way I don't have to keep track of too many controls, and the CustomLabel displays everything needed.

Code for CustomLabel:

public class CustomLabel : Label
{
// Constructors

    // Default Constructor
    public CustomLabel() : base() { }

    public CustomLabel(bool drawBorder, int borderThickness, Color borderColor, Color textColor) : base()
    {
        if (drawBorder)
        {
            BorderThickness = borderThickness;
            BorderColor = borderColor;
        }

        Size = new Size(36, 36);
        Text = "Info";
        Anchor = (AnchorStyles.Left | AnchorStyles.Right);
        AutoSize = false;
        TextAlign = ContentAlignment.MiddleCenter;
        Enabled = false;
        Font = new Font("Microsoft Sans Serif", 6);
        ForeColor = TextColor;
        BorderStyle = BorderStyle.FixedSingle;
        Dock = DockStyle.Fill;

    }

    // Creates a border of specified thickness and color
    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        if (BorderStyle == BorderStyle.FixedSingle)
        {
            int halfThickness = BorderThickness / 2;
            using (Pen p = new Pen(BorderColor, BorderThickness))
            {
                e.Graphics.DrawRectangle(p, new Rectangle(halfThickness,
                     halfThickness,
                     ClientSize.Width - BorderThickness, ClientSize.Height - BorderThickness));
            }
        }
    }

    public int BorderThickness { get; set; }
    public Color BorderColor { get; set; }
}

Code for adding CustomLabel to Panels:

// Add Panels to TableLayoutPanel
for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < cols; j++)
    {
        // Create new Panel
        Panel space = new Panel()
        {
            Size = new Size(45, 45),
            Dock = DockStyle.Fill,
            Margin = new Padding(0),
            ForeColor = Color.Red
        };

        space.MouseClick += new MouseEventHandler(MouseDownOnSpace);

        CustomLabel info = new CustomLabel(false, 0, Color.Empty, Color.Red);  // Create new CustomLabel
        space.Controls.Add(info);   // Add CustomLabel to Panel
        tlp.Controls.Add(space, j, i);      // Add Panel to TableLayoutPanel
    }
}

Code for adjusting the CustomLabel:

((CustomLabel)tlp.GetControlFromPosition(col, row).Controls[0]).BorderThickness = 6;

((CustomLabel)tlp.GetControlFromPosition(col, row).Controls[0]).BorderColor = Color.Yellow;

tlp.GetControlFromPosition(col, row).Controls[0].Text = tlp.GetControlFromPosition(col, row).Controls[0].Text;  // Transfer space information

tlp.GetControlFromPosition(col, row).Refresh();     // Refresh Panel to show changes

This is the end result: (As you can see, the ForeColor still doesn't change.)

enter image description here

Nick08
  • 157
  • 1
  • 2
  • 10
  • If writing graphics code is an option for you, as a good option you can use `CellPaint` event of `TableLayoutPanel` which lets you to draw content of a cell. You can have a model class like `Space` containing some properties representing each cell. Then using `CellPaint`, draw each cell using corresponding model information. Even better is using a completely custom painted custom control which draws model on its surface. – Reza Aghaei Aug 31 '16 at 13:19