-2

I've read about Invoke(ing) controls and whatnot... I don't know WHAT controls I'm supposed to invoke as both my main form and dialog form have more than one, hence my question here. I've read this and this and this ... I simply don't understand how to apply it to my situation. Is there a tutorial somewhere that I go read to try to understand better?

I have a winform app (C#) that does some work. Some of this work may take a while so I thought I'd provide a progress dialog of sorts to alert the user that activity is taking place (and not simply relying on a list control flashing periodically to indicate something updated).

So, I added a new form to my project, added a few items of interest (# of items to process, estimated completion time, current item and an overall progress bar).

public ProgressDialog Progress { get; set; }

public Form1()
{
    Progress = new ProgressDialog(this);
    InitializeComponent();
    backgroundWorker1.WorkerReportsProgress = true;
    backgroundWorker1.WorkerSupportsCancellation = true;
}

I set the main work to be done in a backgroundworker once the Process button is clicked.

private void buttonProcess_Click(object sender, EventArgs e)
{
    if (backgroundWorker1.IsBusy != true)
    {
        backgroundWorker1.RunWorkerAsync();
        Progress.ShowDialog();
    }
}

From a method that is called on that thread, I call a method on my ProgressDialog form:

Progress.UpdateDialog(numFiles: filesToProcess.Count, 
                      processTime: TimeSpan.FromTicks(filesToProcess.Count * TimeSpan.TicksPerSecond * 20)); // 20s is an estimated time, it will be updated after first file is processed.

The code in question there (in ProgressDialog.cs):

public void UpdateDialog(int percentComplete = 0, string fileName = "", int numFiles = 0, TimeSpan? processTime = null)
{
    ...
    if (numFiles > 0)
    {
        labelNumFiles.Text = Convert.ToString(numFiles);
    }
    if (!processTime.Equals(null))
    {
        labelProcessTime.Text = Convert.ToString(processTime);
    }
}

Which results in the following error:

An exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll but was not handled in user code

Additional information: Cross-thread operation not valid: Control 'ProgressDialog' accessed from a thread other than the thread it was created on.

Additionally, the main form has two list controls that need to be updated as the files are processed: a processed file list and an error file list. Had things working fine until I got the brilliant idea to add the progress dialog. LOL So, what is the proper way to handle updating a progress dialog?

Community
  • 1
  • 1
Jon
  • 1,675
  • 26
  • 57
  • Hmmm multi-threading bases could help you... – Noone Mar 25 '14 at 14:52
  • The second link you provided has exactly what you need. Google Background worker ProgressChanged and you will find enough to help you. (second link http://stackoverflow.com/questions/2454900/updating-a-status-on-a-winform-in-backgroundworker ) – Michael B Mar 25 '14 at 14:53
  • 3
    You asked this same question less than an hour ago, apparently you have deleted it. You should not be deleting and then re-asking the same question over and over. As I told you then, BGW already has built in support for this. – Servy Mar 25 '14 at 14:59
  • @Servy I thought I saw this already, too. – tnw Mar 25 '14 at 15:10
  • I have a ReportProgress call and an associated event. I don't even get there because it barks at me earlier when I try to poke some info into the dialog. If I comment out everything involved with updating the dialog, the work then proceeds, but the calls to update my listview controls now no longer work. Is it even possible to update controls other than a progress bar from a background worker? – Jon Mar 25 '14 at 15:12
  • @Jon So then show us the code you're using and explain the error in detail. Note that you should be updating the dialog in the progress reported event, not the do work event. The do work event should have no knowledge of or interaction with the UI. – Servy Mar 25 '14 at 15:13
  • Servy, I did show you the code, but I will recap: In Form1.cs: `private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) { Progress.UpdateDialog(numFiles: filesToProcess.Count); }` and in ProgressDialog.cs: `public void UpdateDialog(int percentComplete = 0, string fileName = "", int numFiles = 0, TimeSpan? processTime = null) { if (numFiles > 0) { // This will generate the error I'm seeing. labelNumFiles.Text = Convert.ToString(numFiles); } }` – Jon Mar 25 '14 at 15:17
  • Sorry that the above kinda looks awful in a comment. Essentially, I need to update the dialog from within the DoWork thread and from the ReportProgress event. – Jon Mar 25 '14 at 15:20
  • First off, edit the question to include relevant information, don't comment. Second, as I told you before, don't update the UI from your `DoWork` handler. That's not its job. It should have no knowledge of the UI. That you're trying to do it is what's wrong, not that it isn't working. – Servy Mar 25 '14 at 15:33
  • I had put all the code into the original question, I simply summarized my question in the previous comment, but it's just a duplication of what I had originally posted. I want to poke various bits of data into the dialog plus I want to update controls on my main form when required, in addition to updating the progress bar, all while the DoWork is running. If DoWork should not do that, then , what should? – Jon Mar 25 '14 at 16:40
  • 1
    @Jon `ProgressChanged` should update the UI with progress. `DoWork` should do work not handle progress changing. – Servy Mar 25 '14 at 18:12
  • So... how is [this guy](http://msdn.microsoft.com/en-us/library/ms951089.aspx) able to update his gui from his worker process and I cannot, or must not? I need to make various updates to two windows (not just a progress bar). – Jon Mar 25 '14 at 18:21
  • I needed an else{} to wrap the check for InvokeRequired. I can now use those kinds of wrappers to update the gui from various places, which suits my needs. The progress percentage is certainly updated from the event handler, but I had update requirements that went beyond updating one control. Seems to be working... need to do some cleanup now. – Jon Mar 25 '14 at 19:12

2 Answers2

0

You have to use Invoke() method to access at user control form a tread different form the one that have created the thread

see this or this for a better trattation of the argument

Community
  • 1
  • 1
abrfra
  • 634
  • 2
  • 6
  • 24
0

The UpdateDialog method needs to be run on the UI thread since it is doing UI work. Since it is being called during the DoWork of your BackgroundWorker (which is most likely not running on your UI thread), you need to do use the InvokeRequired/Invoke pattern for Winforms:

    public void UpdateDialog(int percentComplete = 0, string fileName = "", int numFiles = 0, TimeSpan? processTime = null)
    {
        if (InvokeRequired)
        {
            Invoke(new System.Windows.Forms.MethodInvoker(() => UpdateDialog(percentComplete, fileName, numFiles, processTime)));
        }

        ...
        if (numFiles > 0)
        {
            labelNumFiles.Text = Convert.ToString(numFiles);
        }
        if (!processTime.Equals(null))
        {
            labelProcessTime.Text = Convert.ToString(processTime);
        }
    }
avanek
  • 1,649
  • 12
  • 20
  • So, I plugged that in and the dialog updates. I tried to replicate this for the main form listview controls: `void UpdateProcessedFileListView(string file) { if (InvokeRequired) { Invoke(new System.Windows.Forms.MethodInvoker(() => UpdateProcessedFileListView(file))); } ListViewItem item = new ListViewItem(file); listViewProcessed.Items.Add(item); listViewProcessed.Refresh(); }` And, while the listview updates, the method throws an exception giving me the same cross thread error. – Jon Mar 25 '14 at 16:35
  • What's weirder is that I forgot I was clearing the listview controls at the start of DoWork and that didn't give me a cross thread error... so why would the "Invoked" method throw that exception? – Jon Mar 25 '14 at 17:07
  • What is not clear to me is how you are calling the `UpdateDialog` method. Are you handling the `BackgroundWorker`'s [`ProgressChanged`](http://msdn.microsoft.com/en-us/library/vstudio/system.componentmodel.backgroundworker.progresschanged) event? In your `DoWork` method, you should be calling `ReportProgress` on the `BackgroundWorker`. See [this](http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx) for more info. – avanek Mar 25 '14 at 17:43
  • I am doing just that: calling ReportProgress to update the dialog in the foreground (plus I call it from DoWork to put other info on it, just like I showed in my original question). In DoWork, I am also calling UpdateProcessedFileListView to update a listview on my main form (um, I guess you'd say it was in the background, behind the dialog) with the name of each processed file. Code in the previous comment. (I also have a similar method to update a second listview with the names of files that resulted in an error in DoWork, but I'm just focusing on one at a time.) – Jon Mar 25 '14 at 18:00
  • Okay... me made a dumb mistake... after the check for InvokeRequired, I need an else {} to wrap the rest. LOL Marked as answer and upvoted to cancel the downvote placed there by some dummy. – Jon Mar 25 '14 at 19:08
  • Hey @avanek, Can you explain your () => blah syntax from your answer? That looks totally different than the suggestions I've seen on the web, with using a delegate and passing an object[] containing the arguments. Not sure what that syntax is called so can't even look it up. – Jon Mar 25 '14 at 19:15
  • 1
    @Jon, the `() => ...` is shorthand for an in-line delegate. I suggest reading the [MSDN Lambda Expressions](http://msdn.microsoft.com/en-us/library/bb397687.aspx) article for more info. – avanek Mar 25 '14 at 19:43