【问题标题】:How to implement a progress bar in windows forms C#?如何在 Windows 窗体 C# 中实现进度条?
【发布时间】:2013-09-24 02:02:55
【问题描述】:

我有自己的解决方案,可以在我的 Windows 窗体应用程序中导入月度销售数据。当用户单击import 按钮时,程序实际上正在运行,但看起来它没有响应。该过程需要很长时间,大约 5 分钟。

所以,我想实现一个带有状态条标签的进度条,以显示为用户界面,让用户知道任务完成了多少。这也是我第一次在我的程序中使用进度条。所以,我通读了一些教程,展示了如何使用它。有些人将进度条与后台工作人员和计时器一起使用。

但我不明白应该在哪里使用我拥有的解决方案。在后台工作人员DoWork() 事件中?我不想通过滥用进度条来伪造它,例如设置 progressBar.Maximum = 100,progressBar.Value = 0 并且只要计时器滴答作响,将值增加 5。进度条必须报告实际进度,而程序正在运行。

以下是我现在用来导入数据的解决方案:

private void btnImport_Click(object sender, EventArgs e)
    {
        if (lsbxBrowsedFiles.Items.Count != 0)
        {
            ArrayList salesHeaderArr = new ArrayList();
            ArrayList salesDetailArr = new ArrayList();

            int i = 0;
            while (i < browsedXmlFileList.Count)
            {
                if (browsedXmlFileList[i].ToUpper().EndsWith("SALESHEADER.XML"))
                {
                    salesHeaderArr.Add(browsedXmlFileList[i]);
                }
                if (browsedXmlFileList[i].ToUpper().EndsWith("SALESDETAIL.XML"))
                {
                    salesDetailArr.Add(browsedXmlFileList[i]);
                }
                i++;
            }

            if (selectedFileIsNotInDestinationFolder(salesHeaderArr, salesDetailArr) == true)
            {
                i = 0;
                while (i < salesHeaderArr.Count)
                {
                    SalesHeader salesHeader = new SalesHeader();
                    string sourceFilePath = salesHeaderArr[i].ToString();
                    readXMLFiles(sourceFilePath, SALES_HEADER);
                    SalesHeader salesCheck = (SalesHeader)salesHeaderList[0];
                    string checkOutletCode = salesCheck.OutletCode;
                    DateTime checkBusDate = salesCheck.BusinessDate.Value;
                    if (SalesHeader.IsThisRowAlreadyImportedInSalesHeader(checkOutletCode, checkBusDate) == false)
                    {
                        salesHeader.ImportSalesHeader(salesHeaderList);
                        salesHeader.CreateImportDataLog(getDestinationFilePath(sourceFilePath),
                            DateTime.Now, salesHeaderList.Count, SALES_HEADER);
                    }
                    else
                    {
                        string errorDate = checkBusDate.ToString("dd MMMM, yyyy");
                        MessageBox.Show("Selected XML File with BusinessDate: " + errorDate + " has been already imported.",
                            "ABC Cafe Import Sales Wizard");
                        MessageBox.Show("Please select a file which has not been imported!",
                            "ABC Cafe Import Sales Wizard");
                        return;
                    }
                    MoveXMLFiletoDestinationFolder(sourceFilePath);
                    i++;
                }
                i = 0;
                while (i < salesDetailArr.Count)
                {
                    SalesDetail salesDetail = new SalesDetail();
                    string sourceFilePath = salesDetailArr[i].ToString();
                    readXMLFiles(sourceFilePath, SALES_DETAIL);
                    SalesDetail salesCheck = (SalesDetail)salesDetailList[0];
                    string checkOutletCode = salesCheck.OutletCode;
                    DateTime checkBusDate = salesCheck.BusinessDate.Value;
                    if (SalesDetail.IsThisRowAlreadyImportedInSalesDetail(checkOutletCode, checkBusDate) == false)
                    {
                        salesDetail.ImportSalesDetail(salesDetailList);
                        salesDetail.GenerateCarryForward(salesDetailList);
                        salesDetail.CalculateImportInventoryBalance(salesDetailList);
                        salesDetail.CreateImportDataLog(getDestinationFilePath(sourceFilePath), DateTime.Now, salesDetailList.Count, SALES_DETAIL);
                    }
                    else
                    {
                        string errorDate = checkBusDate.ToString("dd MMMM, yyyy");
                        MessageBox.Show("Selected XML File with BusinessDate: " + errorDate + " has been already imported.",
                            "ABC Cafe Import Sales Wizard");
                        MessageBox.Show("Please select a file which has not been imported!",
                            "ABC Cafe Import Sales Wizard");
                        return;
                    }
                    MoveXMLFiletoDestinationFolder(sourceFilePath);
                    i++;
                }
                MessageBox.Show("Import has been successfully completed!",
                "ABC Cafe Import Sales Wizard");
                clearListBoxItems();
                lblMessage.Visible = false;
            }
            //Abort the import operation here!
            else
            {
                MessageBox.Show("Please select a file which has not been imported!",
                "ABC Cafe Import Sales Wizard");
                clearListBoxItems();
                lblMessage.Visible = false;
            }
        }
        else
        {
            MessageBox.Show("Please select XML files to import!", 
                "ABC Cafe Import Sales Wizard");
        }
    }

任何帮助将不胜感激!

【问题讨论】:

  • 为简单起见考虑使用 async/await 结构。检查这个SO question
  • 只需放下一个进度条,在您的方法开始时将进度条Maximum 属性设置为您将要遍历的文件数量,每次遍历文件时添加一个到进度条Value 属性。如果程序每次迭代都没有响应或显示进度,则在每次文件迭代后也使用Application.DoEvents() 调用。

标签: c# winforms progress-bar backgroundworker implementation


【解决方案1】:

这就是我在应用程序中使用Progress Bar 的方式。

private void btnNext_Click(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.ProgressChanged += (se, eventArgs) => {
            this.progressBar.Maximum = 100;
            this.progressBar.Minimum = 0;
            this.progressBar.Value = eventArgs.ProgressPercentage;
            lblStatus.Text = eventArgs.UserState as String;
            lblPercentage.Text = String.Format("Progress: {0} %", eventArgs.ProgressPercentage);
        };

worker.DoWork += (se, eventArgs) => {
    int progress = 0;
    ((BackgroundWorker)se).ReportProgress(progress, "Initializing the files...");

    //Process that takes a long time
    //Formula to calculate Progress Percentage 
    //This is how I calculated for my program. Divide 100 by number of loops you have

    int findPercentage = ((i + 1) * 100) / salesHeaderArr.Count;
    progress = 0;
    progress += findPercentage / 2;

    //Report back to the UI
    string progressStatus = "Importing Sales Header... (" + getSourceFileName(sourceFilePath) + ")";     
    ((BackgroundWorker)se).ReportProgress(progress, progressStatus);

   //After Iterating through all the loops, update the progress to "Complete"
   ((BackgroundWorker)se).ReportProgress(100, "Complete...");
};
worker.RunWorkerCompleted += (se, eventArgs) =>
        {
            //Display smth or update status when progress is completed
            lblStatus.Location = new Point(20, 60);
            lblStatus.Text = "Your import has been completed. \n\nPlease Click 'Finish' button to close the wizard or \n'Back' button to go back to the previous page.";
            lblPercentage.Visible = false;
            progressBar.Visible = false;
            btnBack.Enabled = true;
            btnFinish.Enabled = true;
        };

        worker.RunWorkerAsync();
}

【讨论】:

  • 你从哪里得到i
  • @SiKni8 你可以在我的问题中找到它。 i 用作SalesHeaderSalesDetail 循环中的迭代。我在我的解决方案中使用的公式就是这样工作的。如果您有 2 个循环,请将您的百分比除以 2。在第一个循环之后,您将拥有 50% 的进程。在第二个循环中,使用相同的公式,但从第一个循环中添加 50。
  • 哎呀没有意识到您回答了自己的问题。谢谢。
【解决方案2】:

您的程序有很多循环,因为在每次循环迭代中很难获得增量值并增加进度条值。您可以将进度条最大值设置为 100,然后将 100 除以您拥有的循环数说 X。

所以诀窍是在每个循环完成时用这个值填充进度条

是的,您应该将此代码放在后台工作人员的DoWork() 中,否则它将冻结表单。而且不需要计时器。

【讨论】:

  • 感谢您的解释。你能提供一些这样做的例子吗?我有点困惑。在这种情况下只有 2 个循环。 SalesHeader 和 SalesDetail。在每个循环中,我使用int percents = ((i + 1) * 100) / salesDetailArr.Count; 获取百分比,使用backgroundWorker.ReportProgress(percents, i); 报告进度。但是,它没有向backgroundWorker_ProgressChanged 方法报告,进度条增量保持为0。在第二个循环内,当程序执行 ImportSalesDetail() 方法时,它增加到 50。请问我做错了什么吗?
  • 你添加了委托吗?我建议你删除 % 的东西并继续我建议的方法。虽然它可能不会显示确切的进度,但它会报告进度并且不需要'backgroundWorker.ReportProgress'
  • 我将您的答案标记为正确答案。我使用这个公式来找到进度百分比。 int findPercentage = ((i + 1) * 100) / salesHeaderArr.Count; 由此,我得到了进度百分比。 progress = 0我将进度重置为0。progress += findPercentage / 2;我的程序中有2个循环。所以,我除以 2。所以,如果第一个循环完成运行,我将得到 50%。在另一个循环中。我使用相同的公式,但这里'progress += (findPercentage / 2) + 50;'我从第一个循环中添加了 50。现在在你的帮助下,我已经完成了我的实现。谢谢。
【解决方案3】:

您可以使用ManualResetEvent,现在在您处理时,让Progressbar 增加直到它遇到某个点并等待Set。

例子:

你有这 2 个字段

private int progress = 0;
private ManualResetEvent reset = new ManualResetEvent(false); 
// Sets it to unsignalled
private ManualResetEvent reset2 = new ManualResetEvent(false);

while(progress  < 40)
{
   progress ++;
}

reset.WaitOne();

while(progress < 90)
{
   progress ++;
}

reset2.WaitOne();

while(progress < 100)
{
   progress ++;
}

// 这完成了进度,现在在你的实际工作中,你必须发出等待的信号。

DoWork()
{
   // long process here. . . .

   reset.Set();

   // Another long process

   reset2.Set();
}

【讨论】:

  • 我不是给你举个例子吗?它在类上声明,现在在你的backgroundworker上,运行while循环并在达到某个值后,使用manualresetevent等待,然后在你的实际进程中设置它,如果你想处理进度条。请阅读并吸收它>.>,我刚刚给出了答案,你要求我做更多的工作?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多