我有一个我写过的辅助类,可以用来在我的GUI上运行一个长时间运行的任务。它的作用是使用样式显示“工作”动画并淡化内容,以便在任务运行时,用户可以看到某些内容正在进行中。
我的问题是,当长时间运行的任务完成时,它会将内容淡入并隐藏工作动画 - 这应该是它应该做的,但是因为我使用MVVM并主要用于所有内容显示的数据绑定, GUI组件的更新分别发生在长时间运行的任务中。即,数据绑定OnPropertyChanged(“”)事件触发,然后在长时间运行的任务完成后由GUI线程拾取这些事件。但问题是当长时间运行的任务完成时工作者动画关闭,但是在数据绑定更新之前。
因此,最终结果是您在任务运行时按预期显示工作者动画,但对于所有树数据的大型数据集,数据绑定更新需要4-5秒甚至更长时间,并且在此期间,应用程序不处于“工作动画模式”并且只是冻结。
有没有办法可以让我的工作者动画不仅继续运行Long运行方法,还运行OnPropertyChanged的关联数据绑定更新?
答案 0 :(得分:0)
考虑使用Extended WPF工具包中的BusyIndicator。它应该提供您描述的功能。它具有IsBusy属性,您可以将其绑定到ViewModel中的属性,并在完成所有工作后将其设置为 False 。 您可以像使用其他控件一样更改BusyIndicator的样式。在我的解决方案中,我总是将此控件与来自 System.ComponentModel 的BackgroundWorker类一起使用,我通常在 RunWorkerCompleted <的末尾设置 IsBusy = false / p>
private void LongRunningMethod()
{
this.IsBusy = true;
var worker = new BackgroundWorker();
worker.DoWork += this.LongMethodDoWork;
worker.RunWorkerCompleted += this.RunWorkerCompleted;
worker.RunWorkerAsync();
}
private void LongMethodDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
{
...
}
private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs runWorkerCompletedEventArgs)
{
...
this.IsBusy = false;
}
答案 1 :(得分:0)
谢谢大家的答案。我实际上遇到了一个可能有点争议的解决方案,因为有些人会解释它有点像黑客攻击,但它完全符合我的要求,并且似乎没有其他方法可以做到这一点,所以对我来说这是一个代码解决方案,而不是一个黑客。
我正在使用我从codeproject(我认为)下载的WPFBackgroundProgressIndicator开源项目,该项目可以选择在主要内容中显示繁忙指示符,有或没有淡出,或者作为弹出窗口,它作为背景运行线程是理想的,为什么我选择它。
问题是,当你运行一个长时间运行的方法时,代码执行同步完成,但所有绑定OnPropertyChanged(“”)更新在Dispatcher线程上异步运行并排队,所以你的工作方法在WPF控件有一个之前完成有机会调用依赖项属性的Getters,以检索新值。您需要做的是有效地“阻止”直到所有Dispatcher事件都已完成,这就是为什么不是每个人都会喜欢这个解决方案,因为它“阻塞”,但那正是我想要做的。我想阻止应用程序直到完整更新完成,因为我不希望用户能够在数据仍然呈现时以可视方式执行任何操作,因此这是我的要求。干净的阻止比杂乱的互动更可取。
所以解决方案,不管你信不信,只是在工作方法调用之后的一行代码。它如下。
Application.Current.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);
您可以看到有效地在Dispatcher线程上排队新任务并阻止当前代码执行直到完成,但是当您给它最低优先级时,此调用将等待所有OTHER调度程序执行完成,即所有呈现完成。渲染完成后,将执行此行,您将退出并完成所有渲染。我在上下文中使用的完整方法如下。我欢迎你对这种方法的想法和讨论。
public void LongRunningTaskWithFade(BusyDecorator busy, Action longTask)
{
if (loading) return;
loading = true;
busy.FadeTime = TimeSpan.Zero;
busy.IsBusyIndicatorShowing = true;
// in order for setting the opacity to take effect, you have to delay the task slightly to ensure WPF has time to process the updated visual
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
longTask();
Application.Current.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null);
}
finally
{
HideBusyDisplay(busy);
}
}), DispatcherPriority.Background);
}