How can I update a list box using an asynchronous call?
I have developed a Windows C forms application, I just want to update the items in the Listbox in the main form, spinning another thread without blocking the GUI form. Since threads cannot access forms like listbox, I thought about using delegates. The following code below shows how I used a delegate to accomplish this task, but it blocks the GUI form. so I just want to convert it to an asynchronous delegate that updates the list without blocking the GUI form
Delegate declaration
delegate void monitoringServiceDel();
delegate invocation
new monitoringServiceDel(monitoringService).BeginInvoke(null, null);
delegate method implementation
private void monitoringService()
{
this.listEvents.Invoke(new MethodInvoker(delegate()
{
int i = 0 ;
while (i<50)
{
listEvents.Items.Add("count :" + count++);
Thread.Sleep(1000);
i ++;
}
}));
}
source to share
For Win Forms, you need to use the Invoke method :
Executes the specified delegate on the thread that owns the window's bottom handle
Main scenario:
- Do the heavy lifting with the BackgroundWorker to fetch all of your elements on a non-UI blocking thread.
- On BackgroundWorker.RunWorkerCompleted Event, use the Control Invoke method to add items to the control (ListBox in your case).
Something line by line:
var bw = new BackgroundWorker();
bw.DoWork += (sender, args) => MethodToDoWork;
bw.RunWorkerCompleted += (sender, args) => MethodToUpdateControl;
bw.RunWorkerAsync();
This should get you moving in the right direction.
Edit: working sample
public List<string> MyList { get; set; }
private void button1_Click( object sender, EventArgs e )
{
MyList = new List<string>();
var bw = new BackgroundWorker();
bw.DoWork += ( o, args ) => MethodToDoWork();
bw.RunWorkerCompleted += ( o, args ) => MethodToUpdateControl();
bw.RunWorkerAsync();
}
private void MethodToDoWork()
{
for( int i = 0; i < 10; i++ )
{
MyList.Add( string.Format( "item {0}", i ) );
System.Threading.Thread.Sleep( 100 );
}
}
private void MethodToUpdateControl()
{
// since the BackgroundWorker is designed to use
// the form UI thread on the RunWorkerCompleted
// event, you should just be able to add the items
// to the list box:
listBox1.Items.AddRange( MyList.ToArray() );
// the above should not block the UI, if it does
// due to some other code, then use the ListBox's
// Invoke method:
// listBox1.Invoke( new Action( () => listBox1.Items.AddRange( MyList.ToArray() ) ) );
}
source to share
If you are modifying a UI element then you are going to block the UI thread. If items end up in packages or require processing between adding each one, you might consider doing the processing behind the scenes (via backgroundworker or Task ). But if you are just taking data and populating a list, then you need to use the UI thread.
source to share
The simplest solution would be to use the control BackgroundWorker
in combination with the two Panels
. The idea is to have one panel in the foreground Visible
when the form loads and have a ImageBox
simple loading gif inside it that plays. ListBox
will be inside another pane that will not be visible by default and will be right behind the first pane.
Once the form is loaded, fire up BackgroundWorker
yours and execute any data received or update data that you need to complete and once the task is complete set the data inside your ListBox and just bring the bar ListBox
and make it visible.
This way you will get a semi-sync loading of yours ListBox
, but not updated after each item is added. You can use this technique at any time, not just when loading the form.
Here's some sample code:
namespace AsyncForm
{
public partial class Form1 : Form
{
private List<String> collectionItems = new List<String>();
public Form1()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 20; i++)
{
((List<String>)e.Argument).Add("Something " + i);
System.Threading.Thread.Sleep(200);
}
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
listBox1.Items.AddRange(collectionItems.ToArray());
listBox1.Visible = true;
pictureBox1.Visible = false;
}
private void Form1_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync(collectionItems);
}
}
}
source to share
You should separate the UI refresh function from the time consuming process.
To handle the logic of the user interface.
private void UpdateUI(string item)
{
if (Thread.CurrentThread.IsBackground)
{
listEvents.Dispatcher.Invoke(new Action(() => //dispatch to UI Thread
{
listEvents.Items.Add(item);
}));
}
else
{
listEvents.Items.Add(item);
}
}
Executing an asynchronous process using TaskParallel
private void Dowork()
{
Task task = Task.Factory.StartNew(() =>
{
int i = 0;
while (i < 10)
{
Thread.Sleep(1000);
UpdateUI(i.ToString());
i++;
}
});
}
source to share