1

I am trying to update a ListBox with large amount of data and keep the UI responsive at the same time.

Here is the code I am using to achieve this, going through 10000 items, collecting them into a 100-item batch and then inserting these 100 items in one go so that I avoid UI update each time a single item is added, but unfortunately the code does not work and the UI is updated only after all the 10000 items are actually added to 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
  • 4,690
  • 11
  • 61
  • 131
  • 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://docs.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
  • 24,585
  • 5
  • 40
  • 69
  • 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