我想创建一个专门的类来更新我的应用程序中的进度条(在本例中为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()
}
}
但可能已被阻止的用户界面未正确更新。显示所有进度的最佳方式是什么?
答案 0 :(得分:1)
Winforms / WPF程序是Eventing
系统。有一个线程可以连续处理事件队列中的事件。这是它的主要工作,理想情况下,这是唯一应该做的事情。任何类型的UI活动都会在事件队列中生成事件 - 就像您将鼠标移到窗口上或单击某个窗口或其他窗口重叠窗口一样,然后当它离开重叠位置时再次生成事件。所有这些事件都由UI线程处理,并始终保持UI 更新。
此外,Winforms / WPF使得必须通过仅在UI线程上允许以线程安全的方式访问和/或更新控件及其属性。
如果您阻止此UI线程或对其执行其他CPU绑定计算,则您的UI 响应性和更新行为将受到影响。最坏情况的UI将冻结。
因此,正确的答案是在另一个工作线程上进行计算循环,并仅通过使用Dispatcher
编组调用UI线程来更新进度条UI。
然而,要回答你的问题并满足你的宗教裁判所,这是可能的 - 但这是不良做法,你应该永远不要做以下...... :
为简单起见,当您更新进度条的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(()=>{}));
}
}
答案 1 :(得分: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;
}
答案 2 :(得分: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
。
答案 3 :(得分:0)
你一直在说你想避免使用线程,我认为因为你不想要不必要的并发症,但这真的不是什么大问题。使操作成为多线程是一件非常简单的事情。即使是非常简短的任务,这也是实现您想要的最简单的方法。使用TPL,它看起来像这样:
using System.Threading.Tasks;
...
Task.Factory.StartNew(() => {
for (...) {
// operation...
progressBar.Dispatcher.BeginInvoke(() => progressBar.Value = ...);
}
});