如何让进度条更新得足够快?

时间:2009-06-10 18:09:25

标签: c# .net-3.5 windows-vista progress-bar

我正在使用进度条向用户显示该过程的进度。它有17个步骤,可能需要大约5秒到2到3分钟,具体取决于天气(井,数据库)

我在XP中没有遇到任何问题,进度条没问题,但是在vista中进行测试时我发现它不再是这种情况。

例如:如果它接近5秒,可能使其成为1/3,然后消失,因为它已经完成。虽然它的进展是17的17,但它没有显示出来。我相信这是因为动画Vista强加于进度条,动画无法快速完成。

有谁知道我怎么纠正这个?

以下是代码:

这是更新进度条的部分,等待是具有进度条的表单。

        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();

这是等待表格:

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);
    }
}

9 个答案:

答案 0 :(得分:33)

Vista在更新进度条时引入了动画效果 - 它尝试从前一个位置平滑地滚动到新设置的位置,这在控件的更新中产生了令人讨厌的时滞。当您以较大的增量跳过进度条时,滞后最明显,例如在一次跳跃中从25%跳到50%。

正如另一张海报所指出的那样,您可以禁用进度条的Vista主题,然后它会模仿XP进度条的行为。

我找到了另一种解决方法:如果你向后设置进度条,它会立即绘制到这个位置。所以,如果你想从25%跳到50%,你会使用(诚然是hackish)逻辑:

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

我知道,我知道 - 这是一个愚蠢的黑客 - 但它确实有效!

答案 1 :(得分:9)

整个混乱的原因是Vista和W7引入的插值动画效果。它与线程阻塞问题毫无关系。调用setProgress()或设置值属性driectly会触发动画效果,我将解释如何作弊:

我想出了一个根据固定值设置最大值的黑客。最大属性不会触发效果,因此您可以通过即时响应自由移动进度。

请记住,实际显示的进度由以下给出:ProgressBar.Value / ProgressBar.Maximum。考虑到这一点,下面的例子将把进度从0移到100,由i:

重新表示
ProgressBar works like this:  
progress = value / maximum

therefore:
maximum = value / progress

我添加了一些所需的缩放因子,应该是自我解释的:

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);
}

答案 2 :(得分:3)

听起来就像你在UI线程上做了所有事情,因此没有释放消息泵。您是否尝试过像BackgroundWorkerProgressChanged事件一样使用smoething?有关示例,请参阅MSDN

BackgroundWorker非常适合加载外部数据 - 但请注意,在返回UI线程之前不应该执行任何数据绑定等(或者只使用Invoke / {{1}将工作推送到UI线程。)

答案 3 :(得分:2)

尝试调用对waiting.setProgess()方法的调用,因为waiting似乎存在于另一个线程中,这将是classic cross thread call(如果你让他,编译器会警告你)。

由于Control.Invoke使用起来有点笨拙,我通常使用一种允许我传递lambda表达式的扩展方法:

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();
            }
        }
    }
}

答案 4 :(得分:1)

我使用Mark Lansdown的优秀答案作为ProgressBar控件的扩展方法。

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;
    }

}

或者您也可以这样做,只将ProgressBar值属性设置两次而不是三次:

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

}

只需使用扩展方法在任何ProgressBar控件上调用它:

this.progressBar.ValueFast(50);

如果您真的想要,您还可以检查当前的Windows环境,并且只执行Windows Vista +代码的黑客部分,因为Windows XP的ProgressBar没有慢速进展动画。

答案 5 :(得分:1)

扩大了Silas Hansen给出的答案,这个似乎每次给我完美的结果。

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;
    }
}

答案 6 :(得分:0)

首先。我永远不会关闭CheckForIllegalCrossThreadCalls选项。

二。更新进度后添加Refresh()。仅仅因为你在不同的线程中工作并不意味着你的GUI线程将会更新。

答案 7 :(得分:0)

我有同样的问题。 我有一个带有多个进度条的表单(前一个是例如文件x / n,底部一个是任务y / m) 顶部进度条不会更新 TIMELY ,而最低进度条会更新 TIMELY 以编程方式我更新它,无效,显式进程消息,刷新或睡眠不修复它。有趣的是底部进度条和其他组件(时间流逝的文本)更新很好。 这纯粹是一个Vista +主题问题(像以前建议的动画,XP或Vista经典主题的工作正常。 在顶部进度条移动到100之后显示消息框(以编程方式,而不是在视觉上),我首先看到消息框,然后我看到完成的进度

我发现了SetWindowTheme(ProgressBar.Handle,'','');如上所述 Disabling progress bar animation on Vista Aero 工作(但我现在有旧式进度条)

答案 8 :(得分:0)

您是否尝试过Application.DoEvents();