0

I have a function, when the function starts, I want to display some UI components and then start working and at the end, I want to erase those components from there. Problem is that I don't see the change in UI on the form.

The function in which I am doing this is:

public void Processor()
{
    // ------------- SETTING UI COMPONENTS 

    lblProgress.Visible = true;
    progressBar.Visible = true;
    btnStop.Visible = true;

    // ------------- WORKING

    int counter = 0, percent = 0;
    foreach (string url in Urls)
    {
        .... WORKING THAT TAKES TIME

        counter += 1;
        percent = ((counter * 100) / Urls.Count());

        // ------------- MODIFYING UI COMPONENTS
        // Modification doesn't appear on the form while running

        lblProgress.Text = "Progress: " + (percent > 100 ? 100 : percent) + "%";
        progressBar.Value = percent;
    }

    // ------------- SETTING UI COMPONENTS

    lblProgress.Visible = false;
    progressBar.Visible = false;
    btnStop.Visible = false;
    lblDone.Visible = true;
}

Can someone help with this. Kindly let me know if I am doing something wrong.

user2831683
  • 967
  • 1
  • 10
  • 21

3 Answers3

5

@user2831683, I think you know now that using Application.DoEvents is not much advisable. Here is another alternative you can use:

async public void Processor() //see the "async" keyword
{
   //SETTING UI COMPONENTS 

   foreach (string url in Urls)
   {
        await Task.Run(() =>
        {
             //.... WORKING THAT TAKES TIME

        });

        //MODIFYING UI COMPONENTS
   }
}

While DoEvents makes your code seem to work, your UI is still blocked by your work most of the time. This approach will allow your UI to make more smooth updates (for ex, while moving your window etc.)

PS: The only change in the original code is the part //.... WORKING THAT TAKES TIME, which is replaced by a Task

EZI
  • 15,209
  • 2
  • 27
  • 33
  • 1
    Yes, this is another good approach - however I think you need to show how the progress bar would get updated as code within the task would need to marshal back to the UI thread. – Enigmativity Jul 16 '15 at 02:10
  • @Enigmativity Just the same in question. No extra code is needed. I don't see any problem, if the OP doesn't access any UI components in his *working* block. – EZI Jul 16 '15 at 02:23
  • The working of the task doesn't manipulate the UI components. As far I understand it might be that the working of the task makes the processor busy for which the UI components get in the block queue and don't respond. – user2831683 Jul 16 '15 at 02:26
  • @user2831683 All of the UI updates are handled by a single thread. If you keep it busy, your interface will be unresponsive. This is the whole idea. – EZI Jul 16 '15 at 02:29
  • 1
    @EZI - The OP is updating a progress bar inside the loop. That needs to be done on the UI thread. – Enigmativity Jul 16 '15 at 02:30
  • @Enigmativity And my code does so (without any extra code) . This is the trick of using async/await – EZI Jul 16 '15 at 02:30
  • @EZI - Sorry, you're right. You've shelled out the code and I was not mentally rebuilding it properly. It might be worth putting back the OP's UI update bits to show explicitly where they go. – Enigmativity Jul 16 '15 at 02:37
  • @Enigmativity I posted it like this just to be able to point out the *await* code. Otherwise It could have been lost in "unrelated codes for this answer".... – EZI Jul 16 '15 at 02:39
3

As an addition to Vimalan's response:

Application.DoEvents() forces to pump the message on the windows message queue. This means that any pending request(s) for UI update are invoked via Application.DoEvents() so this is a brute-force method. I would suggest to make your UI responsive by removing the block of code which is taking time to process and run it on a seprate thread. Right now the code that takes time is also running on the main thread (where the UI is running too.) which I would say choke the main thread and is heaving difficulty to swallow and digest it ;)

Try reading the following asynchronous programming pattern to learn more: https://msdn.microsoft.com/en-us/library/jj152938(v=vs.110).aspx

Or you can use a background worker: https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Joel Legaspi Enriquez
  • 1,236
  • 1
  • 10
  • 14
1

Please do not use .DoEvents(). It can cause all sorts of re-entrancy issues in your code and can break third-party code. Actually it can break the built-in Windows Forms code too, depending on what you're doing.

I would certainly suggest BackgroundWorker as the stock standard approach to resolve this issue.

I, however, prefer to use Microsoft's Reactive Framework for this kind of thing. It makes things like this very simple.

Here's how I would have tackled this:

public void Processor()
{
    // ------------- SETTING UI COMPONENTS 

    lblProgress.Visible = true;
    progressBar.Visible = true;
    btnStop.Visible = true;

    // ------------- WORKING

    Urls
        .ToObservable(Scheduler.Default) // Goes to background thread
        .Do(url =>
        {
            /* .... WORKING THAT TAKES TIME */
        })
        .Select((url, counter) => counter * 100 / Urls.Count())
        .DistinctUntilChanged()
        .ObserveOn(this) // back to the UI thread
        .Subscribe(
            percent => // Done each change in percent
            {
                lblProgress.Text = "Progress: " + (percent > 100 ? 100 : percent) + "%";
                progressBar.Value = percent;
            },
            () => // Done when finished processing
            {
                lblProgress.Visible = false;
                progressBar.Visible = false;
                btnStop.Visible = false;
                lblDone.Visible = true; 
            });
}

You can NuGet "Rx-WinForms" to get all of this goodness.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172