Wednesday, 7 July 2010

WPF: Updating UI Elements Using Dispatcher and BackgroundWorker

If you run a BackgroundWorker object and try updating a UI element in the DoWork method, you'd get this error message:
{"The calling thread cannot access this object because a different thread owns it."}
The reason is that DoWork() method runs in a new thread and the UI elements are owned by the main thread.
A workaround is to use the Dispatcher object as shown in the following example in WPF. When Start button is clicked, a background worker runs and updates the label message:
public partial class Window1 : Window
{
BackgroundWorker aWorker = new BackgroundWorker();

public Window1()
{
InitializeComponent();
aWorker.WorkerSupportsCancellation = true;
aWorker.DoWork += aWorker_DoWork;
aWorker.RunWorkerCompleted += aWorker_RunWorkerCompleted;
}

private delegate void UpdateDelegate(int i);

/// <summary>
/// Handles the DoWork event of the aWorker control.
/// This runs in a new thread
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
private void aWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (int i = 0; i <= 500; i++)
{
Thread.Sleep(100);

if (aWorker.CancellationPending)
{
e.Cancel = true;
return;
}
UpdateDelegate update = new UpdateDelegate(UpdateLabel);
Label1.Dispatcher.BeginInvoke(DispatcherPriority.Normal, update, i);
}
}

private void aWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (!(e.Cancelled))
Label2.Content = "Run Completed!";
else
Label2.Content = "Run Cancelled!";
}

private void UpdateLabel(int i)
{
Label1.Content = "Cycles: " + i.ToString();
}

private void btnStart_Click(object sender, RoutedEventArgs e)
{
aWorker.RunWorkerAsync();
}

private void btnCancel_Click(object sender, RoutedEventArgs e)
{
aWorker.CancelAsync();
}
}