views:

1499

answers:

6

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);
    }
}
+3  A: 

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).

Marc Gravell
It's being updated from a delegate created by another form.
Malfist
But is it on a different thread?
Marc Gravell
I was under the impression that using MethodInvoke created a new thread.
Malfist
Invoke *does* create a new thread, but it also blocks the current one until the work is complete.
Paul Betts
@Paul Betts, MethodInvoker does not block. That's the purpose of it. If it was blocking, why wouldn't I just call the function?
Malfist
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.
Marc Gravell
For completeness, Delegate.Invoke *does* block (as does Control.Invoke). In both cases there is a non-blocking BeginInvoke.
Marc Gravell
I implemented this and it made no difference.
Malfist
A: 

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.

Brad Bruce
I'm quite sure that the GUI thread IS updating if it's not blocked.
VVS
Calling refresh did nothing.
Malfist
+2  A: 

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 http://stackoverflow.com/questions/788828/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();
            }
        }
    }
}
VVS
This made no difference but thank you for showing me that pattern.
Malfist
A: 

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 http://stackoverflow.com/questions/313792/disabling-progress-bar-animation-on-vista-aero works (but I have old style progress bars now)

+6  A: 

Hello,

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!

Mark Lansdown
Yes, this works. Thanks!It's an eyesore in my code, and I hope MS will fix this problem.
Han
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.
drharris
+2  A: 

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);
}
Silas Hansen
Brilliant. This should be the accepted answer.
Nate
I know.. But unfortunately, I think it might seem to complex at first for people to consider trying it out...
Silas Hansen