1

I am trying to update a ListBox with a large amount of data in a way that keeps the user interface (UI) responsive.

To do this, I am using the following code to collect the data into batches of 100 items, and then insert these batches into the ListBox in one go, rather than inserting each item individually. This should prevent the UI from being updated each time an item is added, but unfortunately, the code does not work as expected and the UI is only updated after all of the items have been added to the ListBox.

enter image description here

public partial class Form1 : Form
{
    private SynchronizationContext synchronizationContext;

    public Form1()
    {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        synchronizationContext = SynchronizationContext.Current;

        await Task.Run(() =>
        {
            ConcurrentDictionary<int, int> batch = new ConcurrentDictionary<int, int>();
            int count = 0;
            for (var i = 0; i <= 10000; i++)
            {
                batch[i] = i;
                count++;
                if (count == 100)
                {
                    count = 0;
                    UpdateUI(batch);
                    batch = new ConcurrentDictionary<int, int>();
                }

                
            }
        });
    }

    private void UpdateUI(ConcurrentDictionary<int, int> items)
    {
        synchronizationContext.Post(o =>
        {
            listBox1.SuspendLayout();
            foreach (var item in items)
            {
                listBox1.Items.Add(item.Value);
            }

            listBox1.ResumeLayout();

        }, null);
    }
}
Vahid
  • 5,144
  • 13
  • 70
  • 146
  • 3
    You can replace the whole SynchronizationContext and the Post() method calls with just `BeginInvoke()` (since all methods work in the same Form class. Otherwise, pass a [Progress](https://learn.microsoft.com/en-us/dotnet/api/system.progress-1) delegate: it's a simplified method to Post() to a SynchronizationContext without capturing it explicitly, the `IProgress` delegate does this for you). Then, you don't need `SuspendLayout()` / `ResumeLayout()` (unrelated), but `BeginUpdate()` / `EndUpdate()`. – Jimi Apr 28 '21 at 13:07
  • Maybe this can help : https://stackoverflow.com/questions/2341731/why-wont-control-update-refresh-mid-process – vernou Apr 28 '21 at 13:08
  • 1
    a) AddRange b) why, if you care about UI, add so many items?? – TaW Apr 28 '21 at 14:37

1 Answers1

1

You don't need a multithreading approach in order to update the UI. All you need is to suspend the painting of the ListBox during the mass insert, by using the ListBox.BeginUpdate and ListBox.EndUpdate methods:

private void button1_Click(object sender, EventArgs e)
{
    listBox1.BeginUpdate();
    for (var i = 1; i <= 10000; i++)
    {
        listBox1.Items.Add(i);
    }
    listBox1.EndUpdate();
}

The Control.SuspendLayout and Control.ResumeLayout methods are used when you add controls dynamically in a flexible container, and you want to prevent the controls from jumping around when each new control is added.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Then just: `listBox1.DataSource = Enumerable.Range(0, 10000).Select(i => i).ToArray();`. -- It's true that the OP did not specify whether this collection of items is fetched calling blocking methods, but it's probably the case. The OP should clarify, though, the data source is important – Jimi Apr 28 '21 at 16:09