WPF UI更新细节

时间:2011-11-03 17:09:42

标签: .net wpf multithreading user-interface

我的一般问题的插图:
使用WPF我常常遇到以下问题:在启动UI操作之前,我打开一个视觉反馈控件来显示系统正忙(某种繁忙的指示器);然而,UI在没有显示繁忙指示符之前冻结。

private void MyTree_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        this.MyBusyIndicator.IsBusy = true;
        ...
        //then some UI related operation
        ...
        this.MyBusyIndicator.IsBusy = false;
    }

(我无法将这个耗时的操作移动到另一个线程,因为它是与UI线程相关的操作。)
在寻找解决此类问题的可能解决方案或建议时,我阅读了一些关于某些WPF(甚至.NET框架)UI更新细节的信息,这是造成此类问题的主要原因。

所以我的问题:
什么是WPF(或.NET框架)UI更新机制细节,阻止UI以与代码中定义的完全相同的顺序更新?

它确实只针对WPF吗?

有哪些可行的解决方案或解决方法?

这是一个真正的问题,还是仅仅是我理解不善的结果?

2 个答案:

答案 0 :(得分:3)

问题并非特定于WPF - 大多数UI框架的行为方式相同。

这里的问题是,在UI线程可以处理消息之前,您的UI不会更新。只要您正在进行工作(与UI相关的操作),UI线程就会忙,无法处理重绘屏幕所需的消息。在这种情况下,这意味着繁忙的指标在完成所有操作后才会重绘。

这里真正的解决方案是将操作移动到后台线程中。虽然你说所有这些都是与UI线程相关的,但通常有一种方法可以在WPF中分离出部分内容。关键是要考虑需要很长时间的部件,并将它们移动,只移动它们。这通常意味着加载数据,但不将其设置为UI(即:设置绑定属性),直到完全加载和处理完毕后。

如果完全不可能,唯一真正的选择是设置繁忙指示器,然后使用某种机制推动其他工作,直到UI线程可以处理。这可以通过使用Dispatcher.BeginInvoke将剩余的工作推迟到“稍后”来完成,尽管这有点不太常见。这看起来像是:

private void MyTree_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    this.MyBusyIndicator.IsBusy = true;

    this.Dispatcher.BeginInvoke( (Action) () =>
    {
        // This is now sent to the dispatcher to process later, which gives the busy indicator a chance to refresh...

        //then some UI related operation
        this.MyBusyIndicator.IsBusy = false;
    });
}

话虽这么说,但这并不总是完全可靠 - 因为你仍然在锁定你的UI线程。理想情况下,你永远不应该阻止UI,因为它会阻止重绘正确发生...... DispatcherTimer可以更可靠地工作,但同样,它确实不是一个非常好的方法来处理它。

答案 1 :(得分:0)

在.NET中您可以使用Thread Queue

private void Button_Click( object sender, RoutedEventArgs e )
{
  ThreadPool.QueueUserWorkItem(
        delegate
        {
            // GUI Thread call
            this.Dispatcher.BeginInvoke( 
                    DispatcherPriority.Normal,
                    new Action( () => { this.MyBusyIndicator.IsBusy = true; }) )

            // Action in non-GUI Thread
            ... 

            // GUI Thread call
            this.Dispatcher.BeginInvoke( 
                    DispatcherPriority.Normal,
                    new Action( () => { this.MyBusyIndicator.IsBusy = false; }) )
        } );
   }
}