C# - 使用跑马灯样式的进度条

3
我希望在一个表单上添加一个Marquee样式的ProgressBar,以向用户显示某些操作正在进行。在耗时的操作期间,表单不会更新,因此ProgressBar也会“冻结”。
我已经查看了几篇关于BackgroundWorker的文章,但在我的情况下,该操作不报告进度,这就是为什么我需要一个Marquee bar。
非常感谢任何帮助或代码片段。
注意:我需要使用.NET 4.0(支持XP),所以我不能使用Task.Run :(
button1_Click(object sender, EventArgs e)
{
    progressBar1.Style = ProgressBarStyle.Marquee;
    progressBar1.MarqueeAnimationSpeed = 50;

    // INSERT TIME CONSUMING OPERATIONS HERE
    // THAT DON'T REPORT PROGRESS
    Thread.Sleep(10000);

    progressBar1.MarqueeAnimationSpeed = 0;
    progressBar1.Style = ProgressBarStyle.Blocks;
    progressBar1.Value = progressBar1.Minimum;

}

你知道在开始消耗过程时要执行多少次迭代吗?在这种情况下,我可以为您提供一些实现建议。 - Graffito
这不是迭代,更像是等待MySQL连接或LDAP响应。 - dobragab
4个回答

9
我已经查看了几篇有关BackgroundWorker的文章,但在我的情况下,操作没有报告进度,所以我需要一个滚动条。
您可以使用BackgroundWorker,只是不要使用它的“progress”部分。这两个事情并不互斥...
示例:
    private void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;
        progressBar1.Style = ProgressBarStyle.Marquee;
        progressBar1.MarqueeAnimationSpeed = 50;

        BackgroundWorker bw = new BackgroundWorker();
        bw.DoWork += bw_DoWork;
        bw.RunWorkerCompleted += bw_RunWorkerCompleted;
        bw.RunWorkerAsync();
    }

    void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        // INSERT TIME CONSUMING OPERATIONS HERE
        // THAT DON'T REPORT PROGRESS
        Thread.Sleep(10000);
    }

    void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        progressBar1.MarqueeAnimationSpeed = 0;
        progressBar1.Style = ProgressBarStyle.Blocks;
        progressBar1.Value = progressBar1.Minimum;

        button1.Enabled = true;
        MessageBox.Show("Done!");
    }

听起来不错!是否可以从bw_RunWorkerCompleted访问所有表单控件?(这样它会在同一线程上运行吗?) button1.Enabled = true; MessageBox.Show("完成!"); 这表明是这样的。 - dobragab
是的,你可以安全地RunWorkerCompleted()事件访问所有控件,因为BackgroundWorker控件已经将其调度到主UI线程上。如果将来使用ProgressChanged()事件也是如此。 - Idle_Mind

0
已解决。但是我认为这是处理它的最不优雅的方式。
button1_Click(object sender, EventArgs e)
{
    progressBar1.Style = ProgressBarStyle.Marquee;
    progressBar1.MarqueeAnimationSpeed = 50;


    Task task = Task.Factory.StartNew(() =>
    {
        // INSERT TIME CONSUMING OPERATIONS HERE
        // THAT DON'T REPORT PROGRESS
        Thread.Sleep(10000);
    });

    while (!task.IsCompleted)
    {
         Application.DoEvents();
         Thread.Sleep(1);
    }

    progressBar1.MarqueeAnimationSpeed = 0;
    progressBar1.Style = ProgressBarStyle.Blocks;
    progressBar1.Value = progressBar1.Minimum;

}

3
将点击事件处理程序嵌套在轮询循环中通常是一个非常不好的想法。 - Idle_Mind

0

推荐解决方案

private void button1_Click(object sender, EventArgs e)
{
    button1.Enabled = false;
    progressBar1.Style = ProgressBarStyle.Marquee;
    progressBar1.MarqueeAnimationSpeed = 50;

    Task.Factory.StartNew(() => {
           // INSERT TIME CONSUMING OPERATIONS HERE
           // THAT DON'T REPORT PROGRESS
           Thread.Sleep(10000);
        }, TaskCreationOptions.LongRunning).
            ContinueWith(t => {
                progressBar1.MarqueeAnimationSpeed = 0;
                progressBar1.Style = ProgressBarStyle.Blocks;
                progressBar1.Value = progressBar1.Minimum;

                button1.Enabled = true;
                MessageBox.Show("Done!");
            }, TaskScheduler.FromCurrentSynchronizationContext());
}

顺便提一下,为了处理可能的操作取消,示例实例化了一个CancellationTokenSource对象来生成取消令牌。


0

你仍然需要在不同的线程上运行耗时的工作...你正在UI线程上运行它,这意味着UI没有机会进行任何UI更新(因此你看到了冻结)!

你应该考虑使用Task<>而不是BackgroundWorker

有关Task<>的更多信息,请参见https://msdn.microsoft.com/en-us/library/hh195051%28v=vs.110%29.aspx


如果您无法使用 Task<>,那么您应该回归到 BackgroundWorker 并使用 WorkCompleted 事件来停止跑马灯并将程序移动到下一个操作。

谢谢,我会看一下的。请注意,在编辑中我提到了无法使用Task.Run。在.NET 4.0中是否有任何替代方法? - dobragab

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接