Updating the GUI from background worker

こ雲淡風輕ζ 提交于 2019-12-05 06:08:18

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.

I have written two very easy methods that enable you to invoke your code (only if required) and you only need to write your code once. I think this makes Invoke much friendlier to use:

1) BeginInvoke

public static void SafeBeginInvoke(System.Windows.Forms.Control control, System.Action action)
{
    if (control.InvokeRequired)
        control.BeginInvoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
    else
        action();
}

2) Invoke

public static void SafeInvoke(System.Windows.Forms.Control control, System.Action action)
{
    if (control.InvokeRequired)
        control.Invoke(new System.Windows.Forms.MethodInvoker(() => { action(); }));
    else
        action();
}

It can be called like this:

SafeInvoke(textbox, () => { textbox.Text = "text got changed"; });

Alternatively you could just

System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false;

(which only changes behaviour in debug mode btw) and look if you run into problems.
More often than not you actually don't. It took me quite some time to find cases very Invoke is really required for things not to get messed up.

The basic pattern for updating the UI from another thread is:

If controlItem.InvokeRequired Then
    controlItem.Invoke(Sub() controlItem.Text = textUpdateValue)
Else
    controlItem.Text = textUpdateValue
End If

This could update your list of controls without requiring you to pass anything through ReportProgress. If you would like to update your control from within the thread, I don't believe you need to check InvokeRequired, because it will always be required. However, best practices might be to expose the setting of a control via a property and then to do the full check so you can call it from anywhere.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!