How to Update Gui with Backgroundworker

How to update GUI with backgroundworker?

You need to declare and configure the BackgroundWorker once - then Invoke the RunWorkerAsync method within your loop...

public class UpdateController
{
private UserController _userController;
private BackgroundWorker _backgroundWorker;

public UpdateController(LoginController loginController, UserController userController)
{
_userController = userController;
loginController.LoginEvent += Update;
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
_backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
_backgroundWorker.WorkerReportsProgress= true;
}

public void Update()
{
_backgroundWorker.RunWorkerAsync();
}

public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
// Do the long-duration work here, and optionally
// send the update back to the UI thread...
int p = 0;// set your progress if appropriate
object param = "something"; // use this to pass any additional parameter back to the UI
_backgroundWorker.ReportProgress(p, param);
}
}

// This event handler updates the UI
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Update the UI here
// _userController.UpdateUsersOnMap();
}
}

Updating the GUI from background worker

There's a UserState parameter when calling ReportProgress.

var list_result = new List<List<string>>();

new backgroundWorker1.ReportProgress(0, list_result);

The parameter type is an object so you'll have to cast it back to the type you need:

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var userState = (List<List<string>>)e.UserState;
}

The tricky issue with this is, how do you determine whether you're passing back a List, or a list of lists, or a single string, number, etc. You'll have to test for each possibility in the ProgressChanged event.

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var myList = e.UserState as List<List<string>>;
if (myList != null)
{
// use list
return;
}

int myNumber;
if (Int32.TryParse(e.UserState.ToString(), out myNumber))
{
// use number
return;
}

var myString = e.UserState.ToString();
// use string
}

Alternatively, you could create a class that holds all the values you need (or use Tuple), run everything in the background to populate that class, then pass that to the RunWorkerCompleted event, and update your UI all at once from there.

Can I update UI from BackgroundWorker.RunWorkerCompleted

Your code doesn't have a UI thread. Since you don't have a UI thread, and didn't call RunWorkerAsync from a UI thread (which is required for it to know what UI thread to marshall the event handlers to) it can't call any of the event handlers in a (non-existent) UI thread.

Create a UI application (a winforms or WPF application, for example), make sure to create and run the BGW from a UI thread, and then you'll see that you can manipulate the controls from the various events (other than DoWork, obviously).

Updating UI with BackgroundWorker in WPF

At first you need to support ProgressChanged events.
Update your BackgroundWorker initialization to:

GroupListView.ItemSource = null;
mWorker = new BackgroundWorker();
mWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
mWorker.WorkerSupportsCancellation = true;
mWorker.WorkerReportsProgress = true;
mWorker.ProgressChanged += OnProgressChanged;
mWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
mWorker.RunWorkerAsync(SiteURLTextBox.Text);

After that you have to add a OnProgressChanged handler:

private void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
FetchDataProgressBar.Value = e.ProgressPercentage;
ListViewItem toAdd = (ListViewItem)e.UserState;
toAdd.MouseLeftButtonUp += item_MouseLeftButtonUp;
GroupListView.Items.Add(toAdd);
}

Therefore you have to change your DoWork:

private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
try
{
using (SPSite site = new SPSite((String)e.Argument))
{
SPWeb web = site.OpenWeb();
SPGroupCollection collGroups = web.SiteGroups;
if(GroupNames == null)
GroupNames = new List<string>();
int added = 0;
foreach(SPGroup oGroup in collGroups)
{
added++;
ListViewItem tmp = new ListViewItem() {
Content = oGroup.Name
};
worker.ReportProgress((added * 100)/collGroups.Count,tmp);
}
}
}
catch (Exception ex)
{
MessageBox.Show("Unable to locate a SharePoint site at: " + siteUrl);
}
}

That's because you're not allowed to change GUI on DoWork.

After that, each ListViewItem is added separately to your ListView. I would also recommend, that your URL is passed as an argument to RunWorkerAsync.

Edit: Add percentage to OnProgressChanged.

Using background worker and updating UI

If you need to update some UI components from a background thread you should use the Dispatcher in order to marshal the call to the main thread. This being said, you seem to be trying to update some progress bar. The BackgroundWorker class already provides a mechanism for that. So you can simply have a ProgressBar control on your form and then:

var bgWorker = new BackgroundWorker();

// Specify that this worker supports progress
bgWorker.WorkerReportsProgress = true;

bgWorker.OnProgressChanged += e =>
{
// This is called on the main thread. It safe to update your UI components here
myProgressBar.Value = e.ProgressPercentage;
};

bgWorker.DoWork += (s, e) =>
{
// Do something long lasting
for(int i = 0; i < x; i++) {
//Performing action i of x

// While doing so notify the subscribers of the progress event
var progress = (int)((i / x) * 100.0);
bgWorker.ReportProgress(progress);
}
};

bgWorker.RunWorkerCompleted += (s, e) =>
{
//Finish up things
};

bgWorker.RunWorkerAsync();

BackgroundWorker thread to Update WinForms UI

You should use the ProgressChanged-Event to update the UI. The code for the BackgroundWorker should look something like:

internal static void RunWorker()
{
int speed = 100;
BackgroundWorker clickThread = new BackgroundWorker
{
WorkerReportsProgress = true
};
clickThread.DoWork += ClickThreadOnDoWork;
clickThread.ProgressChanged += ClickThreadOnProgressChanged;
clickThread.RunWorkerAsync(speed);

}

private static void ClickThreadOnProgressChanged(object sender, ProgressChangedEventArgs e)
{

someLabel.Text = (string) e.UserState;

}

private static void ClickThreadOnDoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
int speed = (int) e.Argument;

while (!worker.CancellationPending)
{
Thread.Sleep(speed);
Mouse.DoMouseClick();
Counter++;
worker.ReportProgress(0, "newText-Parameter");
}
}

}

Update GUI using BackgroundWorker

The ProgressChanged event is raised on the UI thread, not the worker thread. In your code, the worker thread is doing almost nothing (just loop from 0 to 10000 and call ReportProgress), most of the work is done on the UI thread. Basically, you're sending too many progress notifications. Because of this, the UI thread is almost always busy and has no time to render the new content of the label.

Rendering in WPF is not performed immediately when you change a property of a control, it is done on a separate dispatcher frame, which is processed when the dispatcher has nothing more urgent to do, based on the priority of the task. The priority used for rendering has a value of 7 (DispatcherPriority.Render); the ProgressChanged event is marshalled to the UI thread with a priority of 9 (DispatcherPriority.Normal), as specified on MSDN. So the ProgressChanged notifications always have a higher priority than rendering, and since they keep coming, the dispatcher never has time to process the rendering tasks.

If you just decrease the frequency of the notifications, your app should work fine (currently you're sending 100 notifications for each percentage value, which is useless):

    void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10000; i++)
{
if (i % 100 == 0)
bw.ReportProgress(i / 100);
}
}


Related Topics



Leave a reply



Submit