0

我想创建一个专门的类来更新我的应用程序中的进度条(在本例中为 WPF 进度条)。我做了这样的事情:

public class ProgressBarUpdate : IDisposable
{
    private readonly double _delta;
    private int _current;
    private int _total;
    private readonly ProgressBar _pb;

    public ProgressBarUpdate(ProgressBar pb, int total)
    {
        _pb = pb;
        _total = total;
        // the pb.Maximum is a double so it doesn`t get truncated
        _delta = _pb.Maximum / total; 
        _current = 0;
        _pb.Visibility = Visibility.Visible;

    }
    public void Dispose()
    {
        _pb.Visibility = Visibility.Collapsed;
        _current = 0;
    }
    public void UpdateProgress()
    {
        _pb.Value =(int)_delta * (++_current);
    }

我这样使用(在 UI 线程中):

using (var pu = new ProgressBarUpdate(pb, totalCount)
{
  for (x=0; x<totalCount; x++)
  {
     // operations here 
     pu.UpdateProgress()
  }
}

但是可能被阻止的 UI 没有正确更新。显示所有进度的最佳方式是什么?

4

4 回答 4

1

Winforms/WPF 程序是一个Eventing系统。有一个线程持续处理来自事件队列的事件。这是它的主要工作,理想情况下这是它应该做的唯一事情。任何类型的 UI 活动都会在事件队列中生成事件 - 就像您将鼠标移到窗口上或单击某物或某个其他窗口与您的窗口重叠,然后当它离开重叠位置时再次出现。所有这些事件都由 UI 线程处理,并始终保持 UI更新

此外,Winforms/WPF 只允许在 UI 线程上访问和/或更新控件及其属性,因此必须以线程安全的方式访问和/或更新控件及其属性。

如果您阻止此 UI 线程或对其执行其他一些 CPU 限制计算,那么您的 UI响应能力更新行为将受到影响。最坏的情况下 UI 将冻结。

因此,您的正确答案是在另一个工作线程上进行计算循环,并且仅通过使用Dispatcher.

但是,为了回答您的问题并满足您的调查,这是可能的 - 但这是不好的做法,您永远不应该做以下事情......

为简单起见,当您更新进度条的Value属性时,它会使进度条 UI 无效 - 因此,UI 必须更新。因此,假设在事件队列中生成了一个事件,这将导致一些代码运行,从而更新 UI。但是,您在 UI 线程上循环运行 - 因此,除非您的循环结束,否则线程没有机会处理此事件。因此,您看不到任何 UI 更新。Value诀窍是在您对进度条进行下一次更新之前让 UI 线程处理该事件。您可以通过在事件队列中强制调用较低优先级的项目来执行此操作 - 以便在进入下一次迭代之前处理正常和较高优先级的项目。

using (var pu = new ProgressBarUpdate(pb, totalCount))
{
  for (int x = 0; x < totalCount ; x++)
  {
     // operations here 
     pu.UpdateProgress();

     Dispatcher.Invoke(DispatcherPriority.Background, new Action(()=>{}));
  }
}
于 2013-07-29T21:16:04.573 回答
0

You've been saying you want to avoid using threads, I assume because you don't want unnecessary complication, but it's really not a big deal. It's a very simple matter to make an operation multi-threaded. Even for very short and simple tasks, this is the most straightforward way to achieve what you want. Using TPL, it would look something like this:

using System.Threading.Tasks;

...

Task.Factory.StartNew(() => {
    for (...) {
        // operation...
        progressBar.Dispatcher.BeginInvoke(() => progressBar.Value = ...);
    }
});
于 2013-07-29T23:02:35.563 回答
0

尝试这个:

public ProgressBarUpdate(ProgressBar pb, int total)
{
    _pb = pb;
    _total = total;
    _delta = _pb.MaxValue/((double)total);  /make sure you do not truncate delta
    _current = 0;
    _pb.Visibility = Visibility.Visible;

}
public void Dispose()
{
    _pb.Visibility = Visibility.Collapsed;
    _current = 0;
}
public void UpdateProgress()
{
    _pb.Value = (int)( _delta * (++_current)); //update after the increment
}

我建议也使用float而不是double.

于 2013-07-29T17:18:04.753 回答
0

如果您正在 UI 线程上进行工作并调用 UpdateProgress,那么在您完成工作并且 UI 线程可以执行其他工作(例如刷新 UI)之前,它不会更新。所以这永远不会奏效。

如果您在后台线程上进行工作,则需要使用 Dispatcher 将值编组到 UI 线程。

这是来自http://tech.pro/tutorial/800/working-with-the-wpf-dispatcher的示例

if (!myCheckBox.Dispatcher.CheckAccess())
{
  myCheckBox.Dispatcher.Invoke(
    System.Windows.Threading.DispatcherPriority.Normal,
    new Action(
      delegate()
      {
        myCheckBox.IsChecked = true;
      }
  ));
}
else
{
  myCheckBox.IsChecked = true;
}
于 2013-07-29T15:08:33.437 回答