How can I make the progress bar update fast enough?
Asked Answered
P

9

10

I'm using a progress bar to show the user how far along the process is. It has 17 steps, and it can take anywhere from ~5 seconds to two or three minutes depending on the weather (well, database)

I had no problem with this in XP, the progress bar went fine, but when testing it in vista I found that it is no longer the case.

For example: if it takes closer to 5 seconds, it might make it a 1/3 of the way through before disappearing because it's completed. Even though it's progress is at 17 of 17, it doesn't show it. I believe this is because of the animation Vista imposes on progress bars and the animation cannot finish fast enough.

Does anyone know how I can correct this?

Here is the code:

This is the part that updates the progress bar, waiting is the form that has the progress bar.

        int progress = 1;
        //1 Cash Receipt Items
        waiting.setProgress(progress, 18, progress, "Cash Receipt Items");
        tblCashReceiptsApplyToTableAdapter1.Fill(rentalEaseDataSet1.tblCashReceiptsApplyTo);
        progress++;
        //2 Cash Receipts
        waiting.setProgress(progress, "Cash Receipts");
        tblCashReceiptsTableAdapter1.Fill(rentalEaseDataSet1.tblCashReceipts);
        progress++;
        //3 Checkbook Codes
        waiting.setProgress(progress, "Checkbook Codes");
        tblCheckbookCodeTableAdapter1.Fill(rentalEaseDataSet1.tblCheckbookCode);
        progress++;
        //4 Checkbook Entries
        waiting.setProgress(progress, "Checkbook Entries");
        tblCheckbookEntryTableAdapter1.Fill(rentalEaseDataSet1.tblCheckbookEntry);
        progress++;
        //5 Checkbooks
        waiting.setProgress(progress, "Checkbooks");
        tblCheckbookTableAdapter1.Fill(rentalEaseDataSet1.tblCheckbook);
        progress++;
        //6 Companies
        waiting.setProgress(progress, "Companies");
        tblCompanyTableAdapter1.Fill(rentalEaseDataSet1.tblCompany);
        progress++;
        //7 Expenses
        waiting.setProgress(progress, "Expenses");
        tblExpenseTableAdapter1.Fill(rentalEaseDataSet1.tblExpense);
        progress++;
        //8 Incomes
        waiting.setProgress(progress, "Incomes");
        tblIncomeTableAdapter1.Fill(rentalEaseDataSet1.tblIncome);
        progress++;
        //9 Properties
        waiting.setProgress(progress, "Properties");
        tblPropertyTableAdapter1.Fill(rentalEaseDataSet1.tblProperty);
        progress++;
        //10 Rental Units
        waiting.setProgress(progress, "Rental Units");
        tblRentalUnitTableAdapter1.Fill(rentalEaseDataSet1.tblRentalUnit);
        progress++;
        //11 Tenant Status Values
        waiting.setProgress(progress, "Tenant Status Values");
        tblTenantStatusTableAdapter1.Fill(rentalEaseDataSet1.tblTenantStatus);
        progress++;
        //12 Tenants
        waiting.setProgress(progress, "Tenants");
        tblTenantTableAdapter1.Fill(rentalEaseDataSet1.tblTenant);
        progress++;
        //13 Tenant Transaction Codes
        waiting.setProgress(progress, "Tenant Transaction Codes");
        tblTenantTransCodeTableAdapter1.Fill(rentalEaseDataSet1.tblTenantTransCode);
        progress++;
        //14 Transactions
        waiting.setProgress(progress, "Transactions");
        tblTransactionTableAdapter1.Fill(rentalEaseDataSet1.tblTransaction);
        progress++;
        //15 Vendors
        waiting.setProgress(progress, "Vendors");
        tblVendorTableAdapter1.Fill(rentalEaseDataSet1.tblVendor);
        progress++;
        //16 Work Order Categories
        waiting.setProgress(progress, "Work Order Categories");
        tblWorkOrderCategoryTableAdapter1.Fill(rentalEaseDataSet1.tblWorkOrderCategory);
        progress++;
        //17 Work Orders
        waiting.setProgress(progress, "Work Orders");
        tblWorkOrderTableAdapter1.Fill(rentalEaseDataSet1.tblWorkOrder);
        progress++;
        //18 Stored procs
        waiting.setProgress(progress, "Stored Procedures");
        getAllCheckbookBalancesTableAdapter1.Fill(rentalEaseDataSet1.GetAllCheckbookBalances);
        getAllTenantBalancesTableAdapter1.Fill(rentalEaseDataSet1.GetAllTenantBalances);
        //getCheckbookBalanceTableAdapter1;
        //getTenantBalanceTableAdapter1;
        getTenantStatusID_CurrentTableAdapter1.Fill(rentalEaseDataSet1.GetTenantStatusID_Current);
        getTenantStatusID_FutureTableAdapter1.Fill(rentalEaseDataSet1.GetTenantStatusID_Future);
        getTenantStatusID_PastTableAdapter1.Fill(rentalEaseDataSet1.GetTenantStatusID_Past);
        selectVacantRentalUnitsByIDTableAdapter1.Fill(rentalEaseDataSet1.SelectVacantRentalUnitsByID);
        getRentBasedBalancesTableAdapter1.Fill(rentalEaseDataSet1.GetRentBasedBalances);
        getAgingBalanceTableAdapter2.Fill(rentalEaseDataSet1.GetAgingBalance);


        waiting.Close();

Here is the waiting form:

public partial class PleaseWaitDialog : Form {
    public PleaseWaitDialog() {
        CheckForIllegalCrossThreadCalls = false;
        InitializeComponent();
    }

    public void setProgress(int current, int max, int min, string loadItem) {
        Debug.Assert(min <= max, "Minimum is bigger than the maximum!");
        Debug.Assert(current >= min, "The current progress is less than the minimum progress!");
        Debug.Assert(current <= max, "The progress is greater than the maximum progress!");

        prgLoad.Minimum = min;
        prgLoad.Maximum = max;
        prgLoad.Value = current;
        lblLoadItem.Text = loadItem;
    }

    public void setProgress(int current, string loadItem) {
        this.setProgress(current, prgLoad.Maximum, prgLoad.Minimum, loadItem);
    }
}
Parrett answered 10/6, 2009 at 18:9 Comment(5)
CheckForIllegalCrossThreadCalls = false <- perhaps you should do the right thing to prevent that error (Invocation) :)Overburdensome
So, the compiler doesn't warn you about cross thread calls?Overburdensome
No, I removed that and fixed it so that there were no cross thread calls.Parrett
If you want a quick and (really) dirty solution, you could use a disabled TrackBar to indicate your progress :pNoahnoak
I make a short video how to quick produce this issue and how to fix it. You can also see this related question, which might help.Luisluisa
O
2

Try invoking the call to the waiting.setProgess() method since waiting seems to live in another thread and this would be a classic cross thread call (which the compiler warns you about if you let him).

Since Control.Invoke is a bit clumsy to use I usually use an extension method that allows me to pass a lambda expression:

waiting.ThreadSafeInvoke(() => waiting.setProgress(...));

.

// also see https://mcmap.net/q/1160340/-invoke-from-different-thread
public static class ControlExtension
{
    public static void ThreadSafeInvoke(this Control control, MethodInvoker method)
    {
        if (control != null)
        {
            if (control.InvokeRequired)
            {
                control.Invoke(method);
            }
            else
            {
                method.Invoke();
            }
        }
    }
}
Overburdensome answered 10/6, 2009 at 19:34 Comment(2)
This made no difference but thank you for showing me that pattern.Parrett
This should not be marked as the answer since it does not change the slow ProgressBar animation on Vista+.Alkalify
I
41

Vista introduced an animation effect when updating the progress bar - it tries to smoothly scroll from the previous position to the newly-set position, which creates a nasty time lag in the update of the control. The lag is most noticeable when you jump a progress bar in large increments, say from 25% to 50% in one jump.

As another poster pointed out, you can disable the Vista theme for the progress bar and it will then mimic the behavior of XP progress bars.

I have found another workaround: if you set the progress bar backwards, it will immediately paint to this location. So, if you want to jump from 25% to 50%, you would use the (admittedly hackish) logic:

progressbar.Value = 50;
progressbar.Value = 49;
progressbar.Value = 50;

I know, I know - it's a silly hack - but it does work!

Inoue answered 31/7, 2009 at 18:35 Comment(6)
Yes, this works. Thanks! It's an eyesore in my code, and I hope MS will fix this problem.Semaphore
Wow. I spent probably 2 hours really working on my threading and ensuring I allowed enough sleep time for UI redraws, and nothing worked. I added this little hack in, and now the progress bar updates cleanly. It's a very dirty hack, but it simply works.Fiertz
Thank you!!! This is the best (albeit dirty) hack I have seen to fix this stupid "Feature"Reamonn
Mark, hackish or not it is a solution! Before this I had a complex equation that would sleep my worker thread for x milliseconds depending on the jump (how big from one value to another). Who needs the fancy slick updating progress bar anyway... There should be a freaking method to force the value, like .ForceValue(50)! ;)Loveinamist
By George, it works. Sometimes we have to implement hacks. I accept that.Townsman
Its 2017, Windows 10 also has this problem with the progress bar. I just used this hack and it works fine :)Beryllium
M
11

The reason for this whole mess is the interpolating animation effect introduced by Vista and W7. It has aboslutely nothing to do with thread blocking issues. Calling setProgress() or setting the Value property driectly, triggers an animation effect to occur, which I will explain how to cheat:

I came up with a hack of setting the maximum according to a fixed value. The maximum property does not trigger the effect, thus you get to freely move the progress around with instant response.

Remember that the actual shown progress is given by: ProgressBar.Value / ProgressBar.Maximum. With this in mind, the example below will move the progress from 0 to 100, repensented by i:

ProgressBar works like this:  
progress = value / maximum

therefore:
maximum = value / progress

I added some scaling factors needed, should be self explanatory:

progressBar1.Maximum *= 100;
progressBar1.Value = progressBar1.Maximum / 100;
for (int i = 1; i < 100; i++)
{
    progressBar1.Maximum = (int)((double)progressBar1.Value / (double)(i + 1) * 100);
    Thread.Sleep(20);
}
Matsu answered 24/11, 2009 at 9:9 Comment(6)
I know.. But unfortunately, I think it might seem to complex at first for people to consider trying it out...Matsu
boooh ... I'm from the future ... This is the best solution. I first did the whole progressbar.Value--; progressbar.Value++; thing. This this works so much better if you give this a shot.Chicane
That goes from 1 to 100, though. Not from 0. Is that factor 100 random, or is that always the same maximum value?Noahnoak
Yes. 100 can be changed to any factor that you want to divide the range into. Try and see if will work with 0 to 100. I think it should, since I divide by (i+1), which should keep it from doing a divisionbyzero exception.Matsu
that thread.sleep(20), still means it's slow it's like it just only shows itself when it reaches the full value.. so if putting it in a form's load, there's still a delay then the form comes up with it at its valueAcreinch
also, are you suggesting this.. perhaps this would be easier for people to use, not having to understand it.. 'cos I don't really fully understand it but I could blackbox it into a method and use it. pastebin.com/raw/GyKQi85eAcreinch
W
3

It sounds like you're doing everything on the UI thread and thus not releasing the message pump. Have you tried using smoething like BackgroundWorker and the ProgressChanged event? See MSDN for an example.

BackgroundWorker is ideal for loading external data - but note that you shouldn't do any data-binding etc until you get back to the UI thread (or just use Invoke/BeginInvoke to push work to the UI thread).

Workroom answered 10/6, 2009 at 18:14 Comment(6)
I was under the impression that using MethodInvoke created a new thread.Parrett
Invoke does create a new thread, but it also blocks the current one until the work is complete.Reynolds
@Paul Betts, MethodInvoker does not block. That's the purpose of it. If it was blocking, why wouldn't I just call the function?Parrett
I was referring to Control.Invoke (i.e. this.Invoke), not Delegate.Invoke; Control.Invoke sends a delegate to the UI thread - useful to update a control periodically while doing background processing.Workroom
For completeness, Delegate.Invoke does block (as does Control.Invoke). In both cases there is a non-blocking BeginInvoke.Workroom
I implemented this and it made no difference.Parrett
O
2

Try invoking the call to the waiting.setProgess() method since waiting seems to live in another thread and this would be a classic cross thread call (which the compiler warns you about if you let him).

Since Control.Invoke is a bit clumsy to use I usually use an extension method that allows me to pass a lambda expression:

waiting.ThreadSafeInvoke(() => waiting.setProgress(...));

.

// also see https://mcmap.net/q/1160340/-invoke-from-different-thread
public static class ControlExtension
{
    public static void ThreadSafeInvoke(this Control control, MethodInvoker method)
    {
        if (control != null)
        {
            if (control.InvokeRequired)
            {
                control.Invoke(method);
            }
            else
            {
                method.Invoke();
            }
        }
    }
}
Overburdensome answered 10/6, 2009 at 19:34 Comment(2)
This made no difference but thank you for showing me that pattern.Parrett
This should not be marked as the answer since it does not change the slow ProgressBar animation on Vista+.Alkalify
A
1

I use Mark Lansdown's excellent answer as an Extension method for the ProgressBar control.

public static void ValueFast(this ProgressBar progressBar, int value)
{
    progressBar.Value = value;

    if (value > 0)    // prevent ArgumentException error on value = 0
    {
        progressBar.Value = value - 1;
        progressBar.Value = value;
    }

}

Or you can also do it this way which only sets the ProgressBar value property twice instead of three times:

public static void ValueFast(this ProgressBar progressBar, int value)
{
    if (value < 100)    // prevent ArgumentException error on value = 100
    {
        progressBar.Value = value + 1;    // set the value +1
    }

    progressBar.Value = value;    // set the actual value

}

Simply call it on any ProgressBar control using the extension method:

this.progressBar.ValueFast(50);

If you really wanted to you could also check the current Windows Environment and only do the hack section of code for Windows Vista+ since Windows XP's ProgressBar does not have the slow progress animation.

Alkalify answered 17/12, 2013 at 17:54 Comment(2)
If the progress bar is used on different occasions for processing lists of items with different lengths, it is usually better to adapt the maximum to the list length, which means you should change your '100' to progressBar.Maximum.Noahnoak
If you're getting an annoying hang at the end, you can increase the maximum, set the value to max, then decrease the value - 1, and set the max = to the value. That seems to fix this.Hagiography
N
1

Expanding on the answer given by Silas Hansen, this one seems to give me perfect results every time.

protected void UpdateProgressBar(ProgressBar prb, Int64 value, Int64 max)
{
    if (max < 1)
        max = 1;
    if (value > max)
        value = max;
    Int32 finalmax = 1;
    Int32 finalvalue = 0;
    if (value > 0)
    {
        if (max > 0x8000)
        {
            // to avoid overflow when max*max exceeds Int32.MaxValue.
            // 0x8000 is a safe value a bit below the actual square root of Int32.MaxValue
            Int64 progressDivideValue = 1;
            while ((max / progressDivideValue) > 0x8000)
                progressDivideValue *= 0x10;
            finalmax = (Int32)(max / progressDivideValue);
            finalvalue = (Int32)(value / progressDivideValue);
        }
        else
        {
            // Upscale values to increase precision, since this is all integer division
            // Again, this can never exceed 0x8000.
            Int64 progressMultiplyValue = 1;
            while ((max * progressMultiplyValue) < 0x800)
                progressMultiplyValue *= 0x10;
            finalmax = (Int32)(max * progressMultiplyValue);
            finalvalue = (Int32)(value * progressMultiplyValue);
        }
    }
    if (finalvalue <= 0)
    {
        prb.Maximum = (Int32)Math.Min(Int32.MaxValue, max);
        prb.Value = 0;
    }
    else
    {
        // hacky mess, but it works...
        // Will pretty much empty the bar for a split second, but this is normally never visible.
        prb.Maximum = finalmax * finalmax;
        // Makes sure the value will DEcrease in the last operation, to ensure the animation is skipped.
        prb.Value = Math.Min(prb.Maximum, (finalmax + 1));
        // Sets the final values.
        prb.Maximum = (finalmax * finalmax) / finalvalue;
        prb.Value = finalmax;
    }
}
Noahnoak answered 7/8, 2015 at 7:51 Comment(1)
I have since expanded this into a full class for handling progress bar updates. This uses delegates to update the UI (as it should), and the Thread.Sleep was removed.Noahnoak
B
0

First. I'd never turn off the CheckForIllegalCrossThreadCalls option.

Second. Add a Refresh() after you update the progress. Just because you're doing work in a different thread doesn't mean your GUI thread is going to get around to updating.

Blind answered 10/6, 2009 at 19:7 Comment(1)
I'm quite sure that the GUI thread IS updating if it's not blocked.Overburdensome
E
0

I have the same problem. I have a form with multiple progress bars (top one is for example file x/n, bottom one is task y/m) The top progress bar does not update TIMELY while the bottom one does Programatically I update it, invalidate, explicit process message, refresh or sleep does not fix it. Funny thing is that bottom progress bar and other component (time elapsed text) updates fine. This is purely a Vista+theme problem (animations like previously suggested, XP or Vista with classic theme works fine. When displaying a message box after the top progress bar has travelled to 100 (programmatically, not visually) I first see the message box and then I see the progress completing

I found that SetWindowTheme(ProgressBar.Handle, ' ', ' '); as explained on Disabling progress bar animation on Vista Aero works (but I have old style progress bars now)

Excrescency answered 26/6, 2009 at 14:9 Comment(0)
J
0

Did you try Application.DoEvents(); ?

Judas answered 12/11, 2015 at 12:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.