【问题标题】:WPF Work-In-Progress animation not displaying for data binding updatesWPF Work-In-Progress 动画不显示数据绑定更新
【发布时间】:2012-11-14 11:36:13
【问题描述】:

我编写了一个辅助类,可用于在我的 GUI 上运行长时间运行的任务。它所做的是使用样式来显示“工作”动画并淡出内容,以便在任务运行时,用户可以看到正在进行的事情。

我的问题是,当长时间运行的任务完成时,它会将内容淡入并隐藏工作动画 - 这是它应该做的,但因为我使用 MVVM 并且主要是数据绑定用于我的所有内容显示, GUI 组件的更新与长时间运行的任务分开进行。即数据绑定 OnPropertyChanged("") 事件触发,然后在长时间运行的任务完成后由 GUI 线程拾取。但问题是当长时间运行的任务完成时,Worker Animation 会关闭,但在数据绑定更新之前。

因此,最终结果是您在任务运行时按预期显示工作动画,但对于所有树数据的大型数据集,数据绑定更新需要 4-5 秒甚至更长的时间,在此期间,应用程序未处于“工作动画模式”,只是冻结。

有没有一种方法可以让我的工作动画继续运行,不仅适用于 Long running Method,而且适用于来自 OnPropertyChanged 的​​相关数据绑定更新?

【问题讨论】:

  • 您是否尝试在长时间运行的方法完成工作时调用 NotifyPropertyChanged 方法?
  • 长时间运行的方法会遍历并更新大量 ViewModel 对象、大量属性。在每个属性的 Setter 中,我调用 OnPropertyChanged(),因此这些都在长时间运行的方法完成之前被调用,因为它们是其中的一部分。是你说的方法吗?我不确定 INotifyPropertyChanged 的​​内部工作原理,但根据观察到的行为,我猜 INotifyPropertyChanged 将调用发送到 GUI 调度程序线程,然后与所有后续线程一起排队,直到长时间运行的工作完成并且 GUI线程释放了吗?
  • 您是否正在执行您的长期任务?在单独的线程、后台工作人员等上?可以发一些代码吗?

标签: wpf data-binding progress


【解决方案1】:

考虑使用扩展 WPF 工具包中的 BusyIndicator。它应该提供您描述的功能。它具有 IsBusy 属性,您可以将其绑定到 ViewModel 中的属性,并在所有工作完成后将其设置为 False。 您始终可以像使用其他控件一样更改 BusyIndi​​cator 的样式。在我的解决方案中,我总是将此控件与 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;
    }

【讨论】:

  • 这仍然无法解决他的问题,因为他希望动画仅在 BINDINGS 完成后完成,而不是在漫长的任务完成后完成
  • 这样我很抱歉错误的答案......我会等待回答并解释如何发现绑定已“完成”的人。我从作者的评论中了解到他的 GUI 线程被冻结了?这不是 BackgroundWorker 的问题。我又错了吗?
【解决方案2】:

感谢大家的回答。我实际上遇到了一个可能有点争议的解决方案,因为有些人会认为它有点像黑客,但它完全符合我的要求,而且似乎没有其他方法可以做到,所以对我来说,这是一个代码解决方案,而不是 hack。

我正在使用我从 codeproject 下载的 WPFBackgroundProgressIndicator 开源项目(我认为),该项目可以选择在主要内容中显示繁忙指示器,带或不带淡出,或作为弹出窗口并作为背景运行理想的线程以及我选择它的原因。

问题在于,当您运行长时间运行的方法时,代码执行同步完成,但所有绑定 OnPropertyChanged("") 更新异步运行并在 Dispatcher 线程上排队,因此您的工作方法在 WPF 控件具有有机会调用依赖属性的 Getter,以检索新值。您需要做的是有效地“阻塞”,直到所有 Dispatcher 事件都完成,这就是为什么不是每个人都会喜欢这个解决方案,因为它“阻塞”,但这正是我想要做的。我想阻止应用程序直到完全更新完成,因为我不希望用户在数据仍在呈现时能够在视觉上做任何事情,所以这是我的要求。干净的阻塞比混乱的交互更可取。

所以不管你信不信,解决方案是在工作方法调用之后的一行代码。如下。

Application.Current.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle, null); 

正如您所看到的那样,它有效地将新任务排队到 Dispatcher 线程并阻止当前代码执行直到它完成,但是当您给它最低优先级时,此调用将等到所有 OTHER Dispatcher 执行完成,即所有渲染完成.渲染完成后,将执行此行,您将在所有渲染完成后退出。我在上下文中使用它的完整方法如下。我欢迎您对此方法的想法和讨论。

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);
        }

【讨论】:

    猜你喜欢
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-02
    • 1970-01-01
    相关资源
    最近更新 更多