Search code examples
c#backgroundworkertablelayoutpanel

TableLayoutPanel update lag


I have a WinForms app which is updating a TableLayoutPanel from a BackgroundWorker. Depending whether a device input is On (1) or off (0) the colour and some text is changed accordingly.

try
{
    //Define a new TLP to hold the search tablelayoutpanel. 
    //search for the iterated card number.
    //get the status label using GetControl from Position.
    TableLayoutPanel TLP = new TableLayoutPanel();
    string IO_Card_Name = "ED527_" + i.ToString();
    TLP = TLP_IO_Info.Controls.Find(IO_Card_Name, true).FirstOrDefault() as TableLayoutPanel;
    try { lbl = TLP.GetControlFromPosition(4, 0) as Label; } catch { lbl = null; };
    //if card is found (because input is active, colour the TLP (card) according to its state.
    if (TLP != null && lbl != null && INPUTS[i - 1] == 1)
    {

        TLP.BackColor = Color.Green;

        foreach (Label l in TLP.Controls)
        {
            l.BackColor = Color.Green;
        }
        lbl.Invoke((MethodInvoker)delegate { lbl.Text = "ON"; });
    }
    else if (TLP != null && lbl != null && INPUTS[i - 1] == 0)
    {
        TLP.BackColor = Color.White;
        foreach (Label l in TLP.Controls)
        {
            l.BackColor = Color.White;
        }
        lbl.Invoke((MethodInvoker)delegate { lbl.Text = "OFF"; });
    }
}
catch (Exception exception)
{
    MessageBox.Show(exception.Message);
};

TLP holds 5 labels in it. The update shows some noticeable lag when the line updates. Is there a way I can carry out something akin to SuspendLayout() / ResumeLayout on the main UI thread?

****EDIT to show the before and after - the IOLabel column updates slightly before the Status column.

Table Layout Panel


Solution

  • Sounds you have a nested design. Each row is 5 Labels are hosted by different TableLayoutPanels and the TLP_IO_Info which is a TableLayoutPanel hosts the other TableLayoutPanels. In the DoWork event of the BackgroundWorker you have a for..loop to change the Backcolor of the inner controls according to the current state of the devices which you read it from the INPUT int array. Please correct me.

    I'd like to suggest this:

    foreach (var tlp in TLP_IO_Info.Controls.OfType<TableLayoutPanel>()
        .Where(x => x.Name.StartsWith("ED527_")))
    {
        if (tlp.GetControlFromPosition(4, 0) is Label lbl)
        {
            var state = // get the state of the current device from INPUT array
            var stateColor = state == 1 ? Color.Green : Color.White;
            var stateText = state == 1 ? "ON" : "OFF";
            this.Invoke(new Action(() =>
            {
                tlp.BackColor = stateColor;
                tlp.Controls.OfType<Label>().ToList().ForEach(l => l.BackColor = stateColor);
                lbl.Text = stateText;
            }));                        
        }
    }
    

    Or this to eliminate the redundant code:

    var stateColors = new[] { Color.White, Color.Green };
    var stateTexts = new[] { "OFF", "ON" };
    
    foreach (var tlp in TLP_IO_Info.Controls.OfType<TableLayoutPanel>()
        .Where(x => x.Name.StartsWith("ED527_")))
    {
        if (tlp.GetControlFromPosition(4, 0) is Label lbl)
        {
            var state = // get the state of the current device from INPUT array
            this.Invoke(new Action(() =>
            {
                tlp.BackColor = stateColors[state];
                tlp.Controls.OfType<Label>().ToList()
                .ForEach(l => l.BackColor = stateColors[state]);
                lbl.Text = stateTexts[state];
            }));
        }
    }
    

    Note that, I've removed the expensive try..catch blocks since this code won't throw any exceptions.

    As for the INPUT array, I suggest that you replace it with a Dictionary<string, int> to store the current state of each device since (according to the link you've provided) each device has a unique IOLineNumber so you can easily set/get the current state of each one.

    ⍰ Maybe there is already something like this in the library?