C# – TableLayoutPanel remove empty rows

ctablelayoutpanelwinforms

I'm trying to remove all empty rows from a TableLayoutPanel. So far I have been able to do it this way

private void RemoveEmptyRows()
{
    for (int row = 0; row < tablePanel.RowCount - 1; row++)
    {
        bool hasControl = false;
        for (int col = 0; col < tablePanel.ColumnCount; col++)
        {
            if (tablePanel.GetControlFromPosition(col, row) != null)
            {
                hasControl = true;
                break;
            }
        }
        if (!hasControl)
            tablePanel.RowStyles.RemoveAt(row);
    }
}

Is there a better way of this? My approach seems too overwhelmed.

Best Answer

I wouldn't say there is a better way except that your code has some flaws. You can rely on Linq to make it little more concise but that may be inelegant as well as less readable and not debuggable at all since you shouldn't be using too much Linq to do this kind of a thing (personally I like it though!). Here is what you should do to your existing code:

private void RemoveEmptyRows()
{
    for (int row = tablePanel.RowCount -1; row >= 0; row--)
    {
        bool hasControl = false;
        for (int col = 0; col < tablePanel.ColumnCount; col++)
        {
            if (tablePanel.GetControlFromPosition(col, row) != null)
            {
                hasControl = true;
                break;
            }
        }

        if (!hasControl)
        {
            tablePanel.RowStyles.RemoveAt(row);
            tablePanel.RowCount--;
        }
    }
}
  1. You should iterate from top to down since RowCount can change when evaluating it against row in your outer foreach loop.

  2. You should also remove the row after removing the RowStyle simply by doing RowCount--.

Here is a Linq version:

Enumerable.Range(0, tablePanel.RowCount)
    .Except(tablePanel.Controls.OfType<Control>()
        .Select(c => tablePanel.GetRow(c)))
    .Reverse()
    .ToList()
    .ForEach(rowIndex =>
     {
         tablePanel.RowStyles.RemoveAt(rowIndex);
         tablePanel.RowCount--;
     });

To break this down as to what it does (if you are not so familiar with Linq):

var listOfAllRowIndices = Enumerable.Range(0, tablePanel.RowCount);
var listOfControlsInTableLayoutPanel = tablePanel.Controls.OfType<Control>();
var listOfRowIndicesWithControl = listOfControlsInTableLayoutPanel.Select(c => tablePanel.GetRow(c));
var listOfRowIndicesWithoutControl = listOfAllRowIndices.Except(listOfRowIndicesWithControl);
var listOfRowIndicesWithoutControlSortedInDescendingOrder = listOfRowIndicesWithoutControl.Reverse(); //or .OrderByDescending(i => i);

And then:

listOfRowIndicesWithoutControlSortedInDescendingOrder.ToList().ForEach(rowIndex =>
{
    tablePanel.RowStyles.RemoveAt(rowIndex);
    tablePanel.RowCount--;
});

Another Linq version which may be more readable, but less efficient:

Enumerable.Range(0, tableLayoutPanel1.RowCount)
    .Where(rowIndex => !tableLayoutPanel1.Controls.OfType<Control>()
        .Select(c => tableLayoutPanel1.GetRow(c))
        .Contains(rowIndex))
    .Reverse()
    .ToList()
    .ForEach(rowIndex =>
    {
        tablePanel.RowStyles.RemoveAt(rowIndex);
        tablePanel.RowCount--;
    });
Related Topic